Skip to content

Commit

Permalink
css: Handle @import rules
Browse files Browse the repository at this point in the history
  • Loading branch information
lauriro committed Jan 3, 2025
1 parent 8977f2e commit 5a83a6e
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 18 deletions.
45 changes: 40 additions & 5 deletions css.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
exports.CSSStyleDeclaration = CSSStyleDeclaration
exports.CSSStyleSheet = CSSStyleSheet

var clearFn = (_, q, str) => q ? (q == "\"" && str.indexOf("'") == -1 ? "'" + str + "'" : _) :
var path = require("path")
, clearFn = (_, q, str) => q ? (q == "\"" && str.indexOf("'") == -1 ? "'" + str + "'" : _) :
_.replace(/[\t\n]+/g, " ")
.replace(/ *([,;{}>~+\/]) */g, "$1")
.replace(/;(?=})/g, "")
Expand Down Expand Up @@ -43,6 +44,7 @@ var clearFn = (_, q, str) => q ? (q == "\"" && str.indexOf("'") == -1 ? "'" + st
}
, ruleTypes = {
style: {
type: 1,
get cssText() {
return this.style.length > 0 ? this.selectorText + "{" + this.style.cssText + "}" : ""
},
Expand All @@ -54,9 +56,32 @@ var clearFn = (_, q, str) => q ? (q == "\"" && str.indexOf("'") == -1 ? "'" + st
},
import: {
get cssText() {
return this.text
var href
, sheet = this.parentStyleSheet
, min = sheet.min
, text = this.text
, urlRe = /(["']).*?\1|url\((['"]?)(?!\/|data:|https?:)(.*?)\2\)/g
, urlFn = (m,q1,q2,u) => q1 ? m : "url('" + path.join(text.baseURI, u) + "')"
if (min && min.import) {
href = path.join(sheet.baseURI, this.href)
text = new CSSStyleSheet({
parentStyleSheet: sheet,
href,
min
}, require("fs").readFileSync(path.resolve(min.root || "", href), "utf8"))
if (sheet.baseURI !== text.baseURI) {
text.rules.forEach(rule => {
if (rule.type === 1) for (let style = rule.style, i = style.length; i--; ) {
if (urlRe.test(style[style[i]])) style[style[i]] = style[style[i]].replace(urlRe, urlFn)
}
})
}
text += ""
}
return text
},
set cssText(text) {
this.href = text.split(/['"()]+/)[1]
this.text = clear(text.replace(/url\(("|')(.+?)\1\)/g, "'$2'"))
}
},
Expand Down Expand Up @@ -100,12 +125,21 @@ function CSSStyleDeclaration(text, parentRule = null) {
return style
}

function CSSStyleSheet(opts) {
function CSSStyleSheet(opts, text = "") {
Object.assign(this, opts)
this.replaceSync(this.ownerNode ? this.ownerNode.textContent : "")
if (opts && opts.href) {
this.baseURI = path.join(
(opts.parentStyleSheet || opts.ownerNode && opts.ownerNode.ownerDocument).baseURI || "",
opts.href,
".."
)
}
this.replaceSync(text)
}

CSSStyleSheet.prototype = {
baseURI: "",
root: "",
disabled: false,
type: "text/css",
deleteRule(idx) {
Expand Down Expand Up @@ -139,7 +173,8 @@ CSSStyleSheet.prototype = {
sheet.rules.push(CSSRule(text.slice(arr[m], arr[m + 1]), sheet, arr[m+2]))
}
},
toString() {
toString(min) {
if (min) this.min = min
return this.rules.map(rule => rule.cssText).filter(Boolean).join("\n")
}
}
Expand Down
2 changes: 1 addition & 1 deletion dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ var boolAttrs = {
if (this.tagName === "STYLE" || this.tagName === "LINK" && this.rel === "stylesheet" && this.href) return new CSSStyleSheet({
href: this.href,
ownerNode: this
})
}, this.tagName === "STYLE" && this.textContent)
},
get style() {
return this._style || (this._style = CSSStyleDeclaration(this.getAttribute("style") || ""))
Expand Down
41 changes: 37 additions & 4 deletions test/css.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("css.js {0}", describe.env === "browser" ? [["mock", exports], ["native
})

describe("CSSStyleSheet", () => {
const sheet = new CSSStyleSheet
const sheet = new CSSStyleSheet()
test("constructor", assert => {
sheet.insertRule(".btn { padding-top: 10px; }")
assert.equal(sheet.rules.length, 1)
Expand Down Expand Up @@ -62,12 +62,45 @@ describe("css.js {0}", describe.env === "browser" ? [["mock", exports], ["native
sheet.replaceSync(text)
assert.equal(sheet.toString(), expected).end()
})

test("@import", [
[ null, "@import 'css/c.css';", "@import 'css/c.css';" ],
[ {}, "@import 'css/c.css';", "@import 'css/c.css';" ],
[ { min: true }, "@import 'css/c.css';", "@import 'css/c.css';" ],
[ { min: { import: true } },
"@import 'test/data/ui/css/c.css';",
".c{content:'url(my-icon.jpg)';cursor:url(test/data/ui/css/my-icon.jpg);background:url(/static/star.gif) bottom right repeat-x blue;mask-image:image(url(https://example.com/images/mask.png),skyblue,linear-gradient(rgb(0 0 0/100%),transparent))}"
],
[ { min: { import: true, root: "test/data/ui" } },
"@import 'css/c.css';",
".c{content:'url(my-icon.jpg)';cursor:url(css/my-icon.jpg);background:url(/static/star.gif) bottom right repeat-x blue;mask-image:image(url(https://example.com/images/mask.png),skyblue,linear-gradient(rgb(0 0 0/100%),transparent))}"
],
[ { min: { import: true, root: "test/data/ui/css" } }, "@import 'c.css';",
".c{content:'url(my-icon.jpg)';cursor:url(my-icon.jpg);background:url(/static/star.gif) bottom right repeat-x blue;mask-image:image(url(https://example.com/images/mask.png),skyblue,linear-gradient(rgb(0 0 0/100%),transparent))}"
],

], (opts, source, result, assert) => {
const sheet = new CSSStyleSheet(opts)
sheet.replaceSync(source)
assert.equal(sheet.toString(), result)
assert.end()
})
})

test("lint", function(assert) {
assert.throws(function() {
const sheet = new CSSStyleSheet()
sheet.replaceSync("a{b:1;}}")
}).end()
})

test("parse and stringify", assert => {
test("parse and stringify", [
[ null ],
[ true ],
[ { import: true, root: "./test/data/ui/css" } ],
], (min, assert) => {
assert.matchSnapshot("./test/data/ui/css/samp1.css", str => {
const sheet = new CSSStyleSheet
sheet.replaceSync(str)
const sheet = new CSSStyleSheet({ min }, str)
return sheet.toString()
})
assert.end()
Expand Down
1 change: 1 addition & 0 deletions test/data/ui/b.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.b { top: 1px; }
1 change: 1 addition & 0 deletions test/data/ui/css/a.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.a { top: 0px; }
8 changes: 8 additions & 0 deletions test/data/ui/css/c.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

.c {
content: "url(my-icon.jpg)";
cursor: url(my-icon.jpg);
background: url('/static/star.gif') bottom right repeat-x blue;
mask-image: image(url("https://example.com/images/mask.png"), skyblue, linear-gradient(rgb(0 0 0 / 100%), transparent));
}

12 changes: 8 additions & 4 deletions test/data/ui/css/samp1.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@

@import url("fineprint.css") print;
@import url("bluish.css") print, screen;
@import "common.css" screen;
@import url("landscape.css") screen and (orientation: landscape);
@import url("a.css") print;
@import url(../b.css) print, screen;
@import 'c.css' screen;
@import url("a.css") screen and (orientation: landscape);
@import url("a.css") supports(display: flex);
@import url("a.css") supports(selector(p:has(a)));
@import url("a.css");


@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
Expand Down
11 changes: 7 additions & 4 deletions test/data/ui/css/samp1.css.snap
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
@import 'fineprint.css' print;
@import 'bluish.css' print,screen;
@import 'common.css' screen;
@import 'landscape.css' screen and (orientation:landscape);
@import 'a.css' print;
@import url(../b.css) print,screen;
@import 'c.css' screen;
@import 'a.css' screen and (orientation:landscape);
@import 'a.css' supports(display:flex);
@import 'a.css' supports(selector(p:has(a)));
@import 'a.css';
@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
@media (min-width:500px){body{color:blue}}
Expand Down
20 changes: 20 additions & 0 deletions test/data/ui/css/samp1.css.snap1
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import 'a.css' print;
@import url(../b.css) print,screen;
@import 'c.css' screen;
@import 'a.css' screen and (orientation:landscape);
@import 'a.css' supports(display:flex);
@import 'a.css' supports(selector(p:has(a)));
@import 'a.css';
@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
@media (min-width:500px){body{color:blue}}
@font-face{font-family:'Trickster';src:local('Trickster'),url(trickster-COLRv1.otf) format('opentype') tech(color-COLRv1),url(trickster-outline.otf) format('opentype'),url(trickster-outline.woff) format('woff')}
@counter-style thumbs{system:cyclic;symbols:'\1F44D';suffix:' '}
.ul,ul{list-style:thumbs;background:fixed bottom right/contain no-repeat #040505 url(ul.svg)}
a{top:1px}
svg|a{top:2px}
*|a{top:3px}
@page{margin:1cm}
@keyframes slidein{from{transform:translateX(0%)}
to{transform:translateX(100%)}}
[data-error]:after{content:'Error ' attr(data-error);color:#eb5757;display:block;font-size:.75em}
20 changes: 20 additions & 0 deletions test/data/ui/css/samp1.css.snap2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.a{top:0px}
.b{top:1px}
.c{content:'url(my-icon.jpg)';cursor:url(my-icon.jpg);background:url(/static/star.gif) bottom right repeat-x blue;mask-image:image(url(https://example.com/images/mask.png),skyblue,linear-gradient(rgb(0 0 0/100%),transparent))}
.a{top:0px}
.a{top:0px}
.a{top:0px}
.a{top:0px}
@namespace url(http://www.w3.org/1999/xhtml);
@namespace svg url(http://www.w3.org/2000/svg);
@media (min-width:500px){body{color:blue}}
@font-face{font-family:'Trickster';src:local('Trickster'),url(trickster-COLRv1.otf) format('opentype') tech(color-COLRv1),url(trickster-outline.otf) format('opentype'),url(trickster-outline.woff) format('woff')}
@counter-style thumbs{system:cyclic;symbols:'\1F44D';suffix:' '}
.ul,ul{list-style:thumbs;background:fixed bottom right/contain no-repeat #040505 url(ul.svg)}
a{top:1px}
svg|a{top:2px}
*|a{top:3px}
@page{margin:1cm}
@keyframes slidein{from{transform:translateX(0%)}
to{transform:translateX(100%)}}
[data-error]:after{content:'Error ' attr(data-error);color:#eb5757;display:block;font-size:.75em}

0 comments on commit 5a83a6e

Please sign in to comment.