|
1 |
| -const cssUrlRegEx = |
2 |
| - /url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g; |
| 1 | +export type StyleElement = HTMLLinkElement | HTMLStyleElement; |
3 | 2 |
|
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 >[] >(); |
21 | 4 |
|
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 | + ); |
30 | 39 | }
|
31 |
| - return styleSheetCache.get( sheetId ); |
| 40 | + return styleSheetCache.get( url ); |
32 | 41 | };
|
33 | 42 |
|
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 ); |
49 | 48 | } );
|
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 |
| - } ); |
67 | 49 | };
|
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 |
| - ); |
0 commit comments