Repository: postcss/postcss-import
Pull request #534 (2023-07-20 12:20:13)

extend and fix data url imports
- add support for url encoded data urls
- fix handling of embedded relative imports

romainmenke: Thank you for the review 🙇 

Patch:
diff --git a/index.js b/index.js
index cc1237d..1871a63 100755
--- a/index.js
+++ b/index.js
@@ -62,6 +62,7 @@ function AtImport(options) {
         state,
         [],
         [],
+        "",
         postcss
       )
 
diff --git a/lib/data-url.js b/lib/data-url.js
index a59c5fb..69c4ce9 100644
--- a/lib/data-url.js
+++ b/lib/data-url.js
@@ -1,14 +1,26 @@
 "use strict"
 
-const dataURLRegexp = /^data:text\/css;base64,/i
+const anyDataURLRegexp = /^data:text\/css(?:;(base64|plain))?,/i
+const base64DataURLRegexp = /^data:text\/css;base64,/i
+const plainDataURLRegexp = /^data:text\/css;plain,/i
 
 function isValid(url) {
-  return dataURLRegexp.test(url)
+  return anyDataURLRegexp.test(url)
 }
 
 function contents(url) {
-  // "data:text/css;base64,".length === 21
-  return Buffer.from(url.slice(21), "base64").toString()
+  if (base64DataURLRegexp.test(url)) {
+    // "data:text/css;base64,".length === 21
+    return Buffer.from(url.slice(21), "base64").toString()
+  }
+
+  if (plainDataURLRegexp.test(url)) {
+    // "data:text/css;plain,".length === 20
+    return decodeURIComponent(url.slice(20))
+  }
+
+  // "data:text/css,".length === 14
+  return decodeURIComponent(url.slice(14))
 }
 
 module.exports = {
diff --git a/lib/parse-statements.js b/lib/parse-statements.js
index aa66e5b..88a90eb 100644
--- a/lib/parse-statements.js
+++ b/lib/parse-statements.js
@@ -20,16 +20,16 @@ function split(params, start) {
   return list
 }
 
-module.exports = function parseStatements(result, styles) {
+module.exports = function parseStatements(result, styles, from) {
   const statements = []
   let nodes = []
 
   styles.each(node => {
     let stmt
     if (node.type === "atrule") {
-      if (node.name === "import") stmt = parseImport(result, node)
-      else if (node.name === "media") stmt = parseMedia(result, node)
-      else if (node.name === "charset") stmt = parseCharset(result, node)
+      if (node.name === "import") stmt = parseImport(result, node, from)
+      else if (node.name === "media") stmt = parseMedia(result, node, from)
+      else if (node.name === "charset") stmt = parseCharset(result, node, from)
     }
 
     if (stmt) {
@@ -39,6 +39,7 @@ module.exports = function parseStatements(result, styles) {
           nodes,
           media: [],
           layer: [],
+          from,
         })
         nodes = []
       }
@@ -52,23 +53,25 @@ module.exports = function parseStatements(result, styles) {
       nodes,
       media: [],
       layer: [],
+      from,
     })
   }
 
   return statements
 }
 
-function parseMedia(result, atRule) {
+function parseMedia(result, atRule, from) {
   const params = valueParser(atRule.params).nodes
   return {
     type: "media",
     node: atRule,
     media: split(params, 0),
     layer: [],
+    from,
   }
 }
 
-function parseCharset(result, atRule) {
+function parseCharset(result, atRule, from) {
   if (atRule.prev()) {
     return result.warn("@charset must precede all other statements", {
       node: atRule,
@@ -79,10 +82,11 @@ function parseCharset(result, atRule) {
     node: atRule,
     media: [],
     layer: [],
+    from,
   }
 }
 
-function parseImport(result, atRule) {
+function parseImport(result, atRule, from) {
   let prev = atRule.prev()
   if (prev) {
     do {
@@ -119,6 +123,7 @@ function parseImport(result, atRule) {
     media: [],
     layer: [],
     supports: [],
+    from,
   }
 
   for (let i = 0; i < params.length; i++) {
diff --git a/lib/parse-styles.js b/lib/parse-styles.js
index 50b74eb..d021dd4 100644
--- a/lib/parse-styles.js
+++ b/lib/parse-styles.js
@@ -17,9 +17,10 @@ async function parseStyles(
   state,
   media,
   layer,
+  from,
   postcss
 ) {
-  const statements = parseStatements(result, styles)
+  const statements = parseStatements(result, styles, from)
 
   for (const stmt of statements) {
     stmt.media = joinMedia(media, stmt.media || [])
@@ -90,6 +91,11 @@ async function resolveImportId(result, stmt, options, state, postcss) {
       postcss
     )
 
+    return
+  } else if (dataURL.isValid(stmt.from)) {
+    // Data urls can't be used a base url to resolve imports.
+    // When the parent statement has a data url
+    // and the current statement doesn't have a data url we ignore the statement.
     return
   }
 
@@ -201,7 +207,16 @@ async function loadImportContent(
   }
 
   // recursion: import @import from imported file
-  return parseStyles(result, styles, options, state, media, layer, postcss)
+  return parseStyles(
+    result,
+    styles,
+    options,
+    state,
+    media,
+    layer,
+    filename,
+    postcss
+  )
 }
 
 module.exports = parseStyles


Test patch:
diff --git a/test/fixtures/data-url.css b/test/fixtures/data-url.css
index 6f1276e..4b2b4b3 100644
--- a/test/fixtures/data-url.css
+++ b/test/fixtures/data-url.css
@@ -4,3 +4,7 @@
 /* Mixed imports: */
 @import url(data:text/css;base64,QGltcG9ydCB1cmwoZm9vLmNzcyk7CgpwIHsKICBjb2xvcjogYmx1ZTsKfQo=);
 @import url(data-url.css);
+
+/* url encoded: */
+@import url(data:text/css;plain,bar%20%7B%20color%3A%20green%20%7D);
+@import url(data:text/css,bar%20%7B%20color%3A%20pink%20%7D);
diff --git a/test/fixtures/data-url.expected.css b/test/fixtures/data-url.expected.css
index 207253e..af160fc 100644
--- a/test/fixtures/data-url.expected.css
+++ b/test/fixtures/data-url.expected.css
@@ -1,24 +1,6 @@
-p { color: green; }
+@import url(foo.css);p { color: green; }p { color: blue; }@media (min-width: 320px){@layer foo{
+p { color: green; } } }@media (min-width: 320px){@layer foo{
 
-p { color: blue; }
-
-@media (min-width: 320px) {
-
- @layer foo {
-p { color: green; } } }
-
-@media (min-width: 320px) {
-
- @layer foo {
-
-p { color: blue; } } }
-
-/* Mixed imports: */
-
-foo{}
-
-p {
+p { color: blue; } } }/* Mixed imports: */p {
   color: blue;
-}
-
-p { color: pink; }
+}p { color: pink; }/* url encoded: */bar { color: green }bar { color: pink }