Skip to content

Commit ed4eafb

Browse files
committed
Clone style elements and enable/disable them
1 parent 5898dac commit ed4eafb

File tree

2 files changed

+49
-82
lines changed

2 files changed

+49
-82
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,49 @@
1-
const cssUrlRegEx =
2-
/url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g;
1+
export type StyleElement = HTMLLinkElement | HTMLStyleElement;
32

4-
const resolveUrl = ( relativeUrl: string, baseUrl: string ) => {
5-
try {
6-
return new URL( relativeUrl, baseUrl ).toString();
7-
} catch ( e ) {
8-
return relativeUrl;
9-
}
10-
};
11-
12-
const withAbsoluteUrls = ( cssText: string, baseUrl: string ) =>
13-
cssText.replace(
14-
cssUrlRegEx,
15-
( _match, quotes = '', relUrl1, relUrl2 ) =>
16-
`url(${ quotes }${ resolveUrl(
17-
relUrl1 || relUrl2,
18-
baseUrl
19-
) }${ quotes })`
20-
);
3+
const styleSheetCache = new Map< string, Promise< StyleElement >[] >();
214

22-
const styleSheetCache = new Map< string, Promise< CSSStyleSheet > >();
23-
24-
const getCachedSheet = async (
25-
sheetId: string,
26-
factory: () => Promise< CSSStyleSheet >
27-
) => {
28-
if ( ! styleSheetCache.has( sheetId ) ) {
29-
styleSheetCache.set( sheetId, factory() );
5+
export const prepareStyles = (
6+
doc: Document,
7+
url: string = ( doc.location || window.location ).href
8+
): Promise< StyleElement >[] => {
9+
if ( ! styleSheetCache.has( url ) ) {
10+
const comment = window.document.createComment( url );
11+
window.document.head.appendChild( comment );
12+
styleSheetCache.set(
13+
url,
14+
[ ...doc.querySelectorAll( 'style,link[rel=stylesheet]' ) ].map(
15+
( element: StyleElement ) => {
16+
if ( doc === window.document ) {
17+
return Promise.resolve( element );
18+
}
19+
if ( element instanceof HTMLStyleElement ) {
20+
const cloned = element.cloneNode(
21+
true
22+
) as HTMLStyleElement;
23+
window.document.head.appendChild( cloned );
24+
cloned.sheet.disabled = true;
25+
return Promise.resolve( cloned );
26+
}
27+
return new Promise( ( resolve, reject ) => {
28+
const cloned = element.cloneNode() as StyleElement;
29+
cloned.onload = () => {
30+
cloned.sheet.disabled = true;
31+
resolve( cloned );
32+
};
33+
cloned.onerror = reject;
34+
window.document.head.appendChild( cloned );
35+
} );
36+
}
37+
)
38+
);
3039
}
31-
return styleSheetCache.get( sheetId );
40+
return styleSheetCache.get( url );
3241
};
3342

34-
const sheetFromLink = async (
35-
{ id, href, sheet: elementSheet }: HTMLLinkElement,
36-
baseUrl: string
37-
) => {
38-
const sheetId = id || href;
39-
const sheetUrl = resolveUrl( href, baseUrl );
40-
41-
if ( elementSheet ) {
42-
return getCachedSheet( sheetId, () => {
43-
const sheet = new CSSStyleSheet();
44-
for ( let i = 0; i < elementSheet.cssRules.length; i++ ) {
45-
const { cssText } = elementSheet.cssRules[ i ];
46-
sheet.insertRule( withAbsoluteUrls( cssText, sheetUrl ), i );
47-
}
48-
return Promise.resolve( sheet );
43+
export const applyStyles = async ( styles: StyleElement[] ) => {
44+
window.document
45+
.querySelectorAll( 'style,link[rel=stylesheet]' )
46+
.forEach( ( el: HTMLLinkElement | HTMLStyleElement ) => {
47+
el.sheet.disabled = ! styles.includes( el );
4948
} );
50-
}
51-
return getCachedSheet( sheetId, async () => {
52-
const response = await fetch( href );
53-
const text = await response.text();
54-
const sheet = new CSSStyleSheet();
55-
await sheet.replace( withAbsoluteUrls( text, sheetUrl ) );
56-
return sheet;
57-
} );
58-
};
59-
60-
const sheetFromStyle = async ( { textContent }: HTMLStyleElement ) => {
61-
const sheetId = textContent;
62-
return getCachedSheet( sheetId, async () => {
63-
const sheet = new CSSStyleSheet();
64-
await sheet.replace( textContent );
65-
return sheet;
66-
} );
6749
};
68-
69-
export const generateCSSStyleSheets = (
70-
doc: Document,
71-
baseUrl: string = ( doc.location || window.location ).href
72-
): Promise< CSSStyleSheet >[] =>
73-
[ ...doc.querySelectorAll( 'style,link[rel=stylesheet]' ) ].map(
74-
( element ) => {
75-
if ( 'LINK' === element.nodeName ) {
76-
return sheetFromLink( element as HTMLLinkElement, baseUrl );
77-
}
78-
return sheetFromStyle( element as HTMLStyleElement );
79-
}
80-
);

packages/interactivity-router/src/index.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { store, privateApis, getConfig } from '@wordpress/interactivity';
66
/**
77
* Internal dependencies
88
*/
9-
import { generateCSSStyleSheets } from './assets/styles';
9+
import { prepareStyles, applyStyles, type StyleElement } from './assets/styles';
1010

1111
const {
1212
directivePrefix,
@@ -41,8 +41,9 @@ interface VdomParams {
4141
}
4242

4343
interface Page {
44+
url: string;
4445
regions: Record< string, any >;
45-
styles: Promise< CSSStyleSheet >[];
46+
styles: Promise< StyleElement >[];
4647
scriptModules: string[];
4748
title: string;
4849
initialData: any;
@@ -85,7 +86,7 @@ const fetchPage = async ( url: string, { html }: { html: string } ) => {
8586
// `router-region` directive.
8687
const regionsToVdom: RegionsToVdom = ( dom, { vdom, baseUrl } = {} ) => {
8788
const regions = { body: undefined };
88-
const styles = generateCSSStyleSheets( dom, baseUrl );
89+
const styles = prepareStyles( dom, baseUrl );
8990
const scriptModules = [
9091
...dom.querySelectorAll< HTMLScriptElement >(
9192
'script[type=module][src]'
@@ -110,7 +111,7 @@ const regionsToVdom: RegionsToVdom = ( dom, { vdom, baseUrl } = {} ) => {
110111
}
111112
const title = dom.querySelector( 'title' )?.innerText;
112113
const initialData = parseServerData( dom );
113-
return { regions, styles, scriptModules, title, initialData };
114+
return { regions, styles, scriptModules, title, initialData, url: baseUrl };
114115
};
115116

116117
// Render all interactive regions contained in the given page.
@@ -123,11 +124,8 @@ const renderRegions = async ( page: Page ) => {
123124
),
124125
] );
125126
// Replace style sheets.
126-
const sheets = await Promise.all( page.styles );
127-
window.document
128-
.querySelectorAll( 'style,link[rel=stylesheet]' )
129-
.forEach( ( element ) => element.remove() );
130-
window.document.adoptedStyleSheets = sheets;
127+
const styles = await Promise.all( page.styles );
128+
applyStyles( styles );
131129

132130
if ( globalThis.IS_GUTENBERG_PLUGIN ) {
133131
if ( navigationMode === 'fullPage' ) {

0 commit comments

Comments
 (0)