Repository: postcss/postcss-import
Pull request #515 (2022-12-04 12:17:08)

add data url support
This change adds support for :

```css
@import url(data:text/css;base64,...)
```

_Before this change a data url would throw an error, so this is not something that is being used today by users of this plugin._

As far as I can tell the change required is minimal and everything just works.
- media queries
- at layer
- sourcemaps
- nesting multiple imports within data urls

---------

I don't this is useful on its own.
Authors should not be base64 encoding CSS source code and storing this in `@import` urls.

It can however be useful for other plugins that have non-standard interactions with `@import`.

Another plugin that does want to provide extra features can choose to store as base64 instead of inlining.

This keeps the source valid with all `@import` rules before anything else and all in the correct order.

---------

An alternative approach to this is that other plugins use the file system.
Instead of inlining they can store the contents in a file and update the `@import` rules so they each is fully standard CSS and everything points to a local file.

_Both approaches have merit and I don't have a strong preference between them._

RyanZim: > An alternative approach to this is that other plugins use the file system.
> Instead of inlining they can store the contents in a file and update the @import rules so they each is fully standard CSS and everything points to a local file.

The problem with this approach is that you'd need to do an absolute path to a temporary file, which will create a dependency message for that file, causing runners to watch those files in watch mode, which is undesirable. I think the data URL approach should be preferred for that reason.

Not sure if anyone will use it, but it's not complicated to add, so no real reason not to.
romainmenke: > The problem with this approach is that you'd need to do an absolute path to a temporary file, which will create a dependency message for that file, causing runners to watch those files in watch mode, which is undesirable. I think the data URL approach should be preferred for that reason.

Cool! Thank you reviewing this 🙇 

Patch:
diff --git a/index.js b/index.js
index d14873d..d324a7e 100755
--- a/index.js
+++ b/index.js
@@ -10,6 +10,7 @@ const loadContent = require("./lib/load-content")
 const processContent = require("./lib/process-content")
 const parseStatements = require("./lib/parse-statements")
 const assignLayerNames = require("./lib/assign-layer-names")
+const dataURL = require("./lib/data-url")
 
 function AtImport(options) {
   options = {
@@ -290,6 +291,14 @@ function AtImport(options) {
       }
 
       function resolveImportId(result, stmt, options, state) {
+        if (dataURL.isValid(stmt.uri)) {
+          return loadImportContent(result, stmt, stmt.uri, options, state).then(
+            result => {
+              stmt.children = result
+            }
+          )
+        }
+
         const atRule = stmt.node
         let sourceFile
         if (atRule.source?.input?.file) {
diff --git a/lib/data-url.js b/lib/data-url.js
new file mode 100644
index 0000000..a59c5fb
--- /dev/null
+++ b/lib/data-url.js
@@ -0,0 +1,17 @@
+"use strict"
+
+const dataURLRegexp = /^data:text\/css;base64,/i
+
+function isValid(url) {
+  return dataURLRegexp.test(url)
+}
+
+function contents(url) {
+  // "data:text/css;base64,".length === 21
+  return Buffer.from(url.slice(21), "base64").toString()
+}
+
+module.exports = {
+  isValid,
+  contents,
+}
diff --git a/lib/load-content.js b/lib/load-content.js
index de61155..c10b57e 100644
--- a/lib/load-content.js
+++ b/lib/load-content.js
@@ -1,5 +1,12 @@
 "use strict"
 
 const readCache = require("read-cache")
+const dataURL = require("./data-url")
 
-module.exports = filename => readCache(filename, "utf-8")
+module.exports = filename => {
+  if (dataURL.isValid(filename)) {
+    return dataURL.contents(filename)
+  }
+
+  return readCache(filename, "utf-8")
+}


Test patch:
diff --git a/test/data-url.js b/test/data-url.js
new file mode 100644
index 0000000..13af7d8
--- /dev/null
+++ b/test/data-url.js
@@ -0,0 +1,8 @@
+"use strict"
+// external tooling
+const test = require("ava")
+
+// internal tooling
+const checkFixture = require("./helpers/check-fixture")
+
+test("should inline data urls", checkFixture, "data-url")
diff --git a/test/fixtures/data-url.css b/test/fixtures/data-url.css
new file mode 100644
index 0000000..6f1276e
--- /dev/null
+++ b/test/fixtures/data-url.css
@@ -0,0 +1,6 @@
+@import url(data:text/css;base64,QGltcG9ydCB1cmwoZGF0YTp0ZXh0L2NzcztiYXNlNjQsY0NCN0lHTnZiRzl5T2lCbmNtVmxianNnZlE9PSk7CgpwIHsgY29sb3I6IGJsdWU7IH0K);
+@import url("DATA:TEXT/CSS;BASE64,QGltcG9ydCB1cmwoZGF0YTp0ZXh0L2NzcztiYXNlNjQsY0NCN0lHTnZiRzl5T2lCbmNtVmxianNnZlE9PSk7CgpwIHsgY29sb3I6IGJsdWU7IH0K") layer(foo) (min-width: 320px);
+
+/* Mixed imports: */
+@import url(data:text/css;base64,QGltcG9ydCB1cmwoZm9vLmNzcyk7CgpwIHsKICBjb2xvcjogYmx1ZTsKfQo=);
+@import url(data-url.css);
diff --git a/test/fixtures/data-url.expected.css b/test/fixtures/data-url.expected.css
new file mode 100644
index 0000000..207253e
--- /dev/null
+++ b/test/fixtures/data-url.expected.css
@@ -0,0 +1,24 @@
+p { color: green; }
+
+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 {
+  color: blue;
+}
+
+p { color: pink; }
diff --git a/test/fixtures/imports/data-url.css b/test/fixtures/imports/data-url.css
new file mode 100644
index 0000000..d680442
--- /dev/null
+++ b/test/fixtures/imports/data-url.css
@@ -0,0 +1,1 @@
+@import url("DATA:text/CSS;BASE64,cCB7IGNvbG9yOiBwaW5rOyB9");