Skip to content

Commit b8e53ff

Browse files
committed
Make relative css imports absolute to allow loading from blobs
1 parent 4d5c91c commit b8e53ff

6 files changed

+112
-2
lines changed

Diff for: index.html

+42
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<link rel="stylesheet" href="/anchor-scope.css" />
4646
<link rel="stylesheet" href="/position-area.css" />
4747
<link rel="stylesheet" href="/anchor-inside-outside.css" />
48+
<link rel="stylesheet" href="/import-has-import.css" />
4849
<!-- Included to test invalid stylesheets -->
4950
<link rel="stylesheet" href="/fake.css" />
5051
<style>
@@ -1328,6 +1329,47 @@ <h2>
13281329
top: anchor(center);
13291330
}</code></pre>
13301331
</section>
1332+
1333+
<section id="imports" class="demo-item">
1334+
<h2>
1335+
<a href="#imports" aria-hidden="true">🔗</a>
1336+
Works with CSS @imports
1337+
</h2>
1338+
<div class="demo-elements">
1339+
<div class="anchor">Anchor</div>
1340+
<div class="target" id="target-1">Target One</div>
1341+
<div class="target" id="target-2">Target Two</div>
1342+
</div>
1343+
<p class="note">
1344+
With polyfill applied: Target and Anchor text is orange (from styles
1345+
defined in an imported stylesheet). Target One is positioned at the
1346+
bottom right corner of the Anchor.<br />
1347+
1348+
<strong>Note:</strong> Target Two has <code>position-area</code> defined
1349+
in an imported stylesheet, which is not parsed by the polyfill, so it is
1350+
not positioned correctly. It should be positioned at the bottom left
1351+
corner.
1352+
</p>
1353+
<pre><code class="language-css">@import url('./import-is-imported-url.css') supports(display: grid) screen and (min-width: 400px);
1354+
@import './import-is-imported-string.css';
1355+
1356+
/* ./import-is-imported-url.css */
1357+
#imports #target-1{
1358+
color: var(--brand-orange);
1359+
}
1360+
1361+
#imports #target-2{
1362+
position-area: block-end inline-start;
1363+
color: var(--brand-orange);
1364+
}
1365+
1366+
/* ./import-is-imported-string.css */
1367+
#imports .anchor{
1368+
color: var(--brand-orange);
1369+
}
1370+
</code></pre>
1371+
</section>
1372+
13311373
<section id="sponsor">
13321374
<h2>Sponsor OddBird’s OSS Work</h2>
13331375
<p>

Diff for: public/import-has-import.css

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@import url('./import-is-imported-url.css') supports(display: grid) screen and
2+
(min-width: 400px);
3+
/* stylelint-disable-next-line import-notation */
4+
@import './import-is-imported-string.css';
5+
6+
#imports .anchor {
7+
anchor-name: --import-anchor;
8+
}
9+
10+
#imports .target {
11+
position: absolute;
12+
position-anchor: --import-anchor;
13+
}
14+
15+
#imports #target-1 {
16+
position-area: end;
17+
}

Diff for: public/import-is-imported-string.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#imports .anchor {
2+
color: var(--brand-orange);
3+
}

Diff for: public/import-is-imported-url.css

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#imports #target-1 {
2+
color: var(--brand-orange);
3+
}
4+
5+
#imports #target-2 {
6+
position-area: block-end inline-start;
7+
color: var(--brand-orange);
8+
}

Diff for: src/parse.ts

+18
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
getAST,
4545
getSelectors,
4646
isAnchorFunction,
47+
makeImportUrlAbsolute,
4748
type StyleData,
4849
} from './utils.js';
4950
import { validatedForPositioning } from './validate.js';
@@ -682,6 +683,23 @@ export async function parseCSS(styleData: StyleData[]) {
682683
}
683684
}
684685

686+
for (const styleObj of styleData) {
687+
if (styleObj.changed) {
688+
let changed = false;
689+
const ast = getAST(styleObj.css);
690+
walk(ast, {
691+
visit: 'Atrule',
692+
enter(node) {
693+
changed = makeImportUrlAbsolute(node);
694+
},
695+
});
696+
if (changed) {
697+
// Update CSS
698+
styleObj.css = generateCSS(ast);
699+
}
700+
}
701+
}
702+
685703
// Store inline style custom property mappings for each target element
686704
const inlineStyles = new Map<HTMLElement, Record<string, string>>();
687705
// Store any `anchor()` fns

Diff for: src/utils.ts

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
Atrule,
23
CssNode,
34
Declaration,
45
FunctionNode,
@@ -7,6 +8,8 @@ import type {
78
Selector as CssTreeSelector,
89
SelectorList,
910
Value,
11+
AtrulePrelude,
12+
StyleSheet,
1013
} from 'css-tree';
1114
import generate from 'css-tree/generator';
1215
import parse from 'css-tree/parser';
@@ -27,9 +30,9 @@ export function isAnchorFunction(node: CssNode | null): node is FunctionNode {
2730
return Boolean(node && node.type === 'Function' && node.name === 'anchor');
2831
}
2932

30-
export function getAST(cssText: string) {
33+
export function getAST(cssText: string, parseAtrulePrelude = false) {
3134
return parse(cssText, {
32-
parseAtrulePrelude: false,
35+
parseAtrulePrelude: parseAtrulePrelude,
3336
parseCustomProperty: true,
3437
});
3538
}
@@ -50,6 +53,25 @@ export function getDeclarationValue(node: DeclarationWithValue) {
5053
return (node.value.children.first as Identifier).name;
5154
}
5255

56+
function isImportRule(node: CssNode): node is Atrule {
57+
return node.type === 'Atrule' && node.name === 'import';
58+
}
59+
60+
export function makeImportUrlAbsolute(node: CssNode): boolean {
61+
if (!isImportRule(node)) return false;
62+
const reparsedNode = (getAST(generateCSS(node), true) as StyleSheet).children.first;
63+
if (!reparsedNode || !isImportRule(reparsedNode)) return false;
64+
if (reparsedNode.prelude?.type !== 'AtrulePrelude') return false;
65+
66+
// URL is always the first child of the prelude
67+
const url = reparsedNode.prelude.children.first;
68+
if (!url) return false;
69+
if (url.type !== 'Url' && url.type !== 'String') return false;
70+
url.value = new URL(url.value, document.baseURI).href;
71+
node.prelude = reparsedNode.prelude;
72+
return true;
73+
}
74+
5375
export interface StyleData {
5476
el: HTMLElement;
5577
css: string;

0 commit comments

Comments
 (0)