Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions plugin-packs/postcss-bundler/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes to PostCSS Bundler

### Unreleased (patch)

- Fix bundling in more cases for `@import` statements that link to external resources

### 2.0.7

_May 27, 2025_
Expand Down
2 changes: 1 addition & 1 deletion plugin-packs/postcss-bundler/dist/index.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion plugin-packs/postcss-bundler/dist/index.mjs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions plugin-packs/postcss-bundler/src/postcss-import/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { postProcess } from './lib/post-process';
const creator: PluginCreator<never> = () => {
return {
postcssPlugin: 'postcss-bundler',
async Once(styles, { result, atRule, postcss }): Promise<void> {
async Once(styles, { result, atRule, root, postcss }): Promise<void> {
const bundle = await parseStyles(
result,
styles,
Expand All @@ -17,7 +17,7 @@ const creator: PluginCreator<never> = () => {
postcss,
);

postProcess(bundle, atRule);
postProcess(bundle, atRule, root);

applyConditions(bundle, atRule);
applyStyles(bundle, styles);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ export function base64EncodedConditionalImport(prelude: string, conditions: Arra
)}`;

for (const condition of conditions) {
params = `'data:text/css;base64,${Buffer.from(
params = `"data:text/css;base64,${Buffer.from(
`@import ${params}`,
).toString('base64')}' ${formatImportPrelude(
).toString('base64')}" ${formatImportPrelude(
condition.layer,
condition.media,
condition.supports,
Expand Down
143 changes: 78 additions & 65 deletions plugin-packs/postcss-bundler/src/postcss-import/lib/post-process.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,105 @@
import type { AtRule, AtRuleProps } from 'postcss';
import type { AtRule, AtRuleProps, Root, RootProps } from 'postcss';
import type { ImportStatement, NodesStatement, Stylesheet} from './statement';
import { isImportStatement, isNodesStatement, isPreImportStatement } from './statement';
import { isImportStatement, isNodesStatement, isPreImportStatement, isWarning } from './statement';

export function postProcess(stylesheet: Stylesheet, atRule: (defaults?: AtRuleProps) => AtRule): void {
export function postProcess(stylesheet: Stylesheet, atRule: (defaults?: AtRuleProps) => AtRule, root: (defaults?: RootProps) => Root): void {
let indexOfFirstImport = -1;
let indexOfFirstPreImport = -1;
let indexOfFirstMeaningfulNode = -1;
let indexOfLastImport = -1;

for (let i = 0; i < stylesheet.statements.length; i++) {
const stmt = stylesheet.statements[i];

if (isImportStatement(stmt)) {
indexOfFirstImport = i;
if (indexOfFirstImport !== -1 && indexOfFirstPreImport !== -1 && indexOfFirstMeaningfulNode !== -1) {
break;
if (indexOfFirstImport === -1) {
indexOfFirstImport = i;
}

indexOfLastImport = i;
continue;
}
}

if (isPreImportStatement(stmt)) {
indexOfFirstPreImport = i;
if (indexOfFirstImport !== -1 && indexOfFirstPreImport !== -1 && indexOfFirstMeaningfulNode !== -1) {
break;
}
for (let i = 0; i < stylesheet.statements.length; i++) {
const stmt = stylesheet.statements[i];

if (isImportStatement(stmt)) {
continue;
}

if (isNodesStatement(stmt)) {
for (let j = 0; j < stmt.nodes.length; j++) {
const node = stmt.nodes[j];
if (node.type === 'comment') {
continue;
}
if (isWarning(stmt)) {
continue;
}

indexOfFirstMeaningfulNode = i;
}
if (isNodesStatement(stmt) && !stmt.importingNode) {
continue;
}

if (isPreImportStatement(stmt)) {
if (i < indexOfLastImport) {
const params = 'data:text/css;base64,' + Buffer.from(stmt.node.toString()).toString('base64');

if (indexOfFirstImport !== -1 && indexOfFirstPreImport !== -1 && indexOfFirstMeaningfulNode !== -1) {
break;
const importStmt: ImportStatement = {
type: 'import',
uri: params,
fullUri: '"' + params + '"',
node: atRule({
name: 'import',
params: '"' + params + '"',
source: stmt.node.source,
}),
conditions: stmt.conditions,
from: stmt.from,
importingNode: stmt.importingNode,
};

stylesheet.statements.splice(i, 1, importStmt);
} else {
const nodesStmt: NodesStatement = {
type: 'nodes',
nodes: [stmt.node],
conditions: stmt.conditions,
from: stmt.from,
importingNode: stmt.importingNode,
};

stylesheet.statements.splice(i, 1, nodesStmt);
}

continue;
}
}

if (indexOfFirstPreImport !== -1) {
for (let i = 0; i < stylesheet.statements.length; i++) {
const stmt = stylesheet.statements[i];

if (isPreImportStatement(stmt)) {
if (
i < indexOfFirstImport &&
(
i < indexOfFirstMeaningfulNode ||
indexOfFirstMeaningfulNode === -1
)
) {
const params = 'data:text/css;base64,' + Buffer.from(stmt.node.toString()).toString('base64');

const importStmt: ImportStatement = {
type: 'import',
uri: params,
fullUri: '\'' + params + '\'',
node: atRule({
name: 'import',
params: '\'' + params + '\'',
source: stmt.node.source,
}),
conditions: stmt.conditions,
from: stmt.from,
importingNode: stmt.importingNode,
};

stylesheet.statements.splice(i, 1, importStmt);
} else {
const nodesStmt: NodesStatement = {
type: 'nodes',
nodes: [stmt.node],
conditions: stmt.conditions,
from: stmt.from,
importingNode: stmt.importingNode,
};

stylesheet.statements.splice(i, 1, nodesStmt);
}
}
if (i < indexOfFirstImport && stmt.nodes.every((x) => x.type === 'atrule' && !x.nodes)) {
continue;
}

if (i < indexOfLastImport && (stmt.nodes.every((x) => x.type === 'comment'))) {
continue;
}

if (i < indexOfLastImport) {
const dummyRoot = root();
stmt.nodes.forEach((node) => {
node.parent = undefined;
dummyRoot.append(node);
});

const params = 'data:text/css;base64,' + Buffer.from(dummyRoot.toString()).toString('base64');

const importStmt: ImportStatement = {
type: 'import',
uri: params,
fullUri: '"' + params + '"',
node: atRule({
name: 'import',
params: '"' + params + '"',
source: stmt.importingNode?.source ?? stmt.nodes[0]?.source,
}),
conditions: stmt.conditions,
from: stmt.from,
importingNode: stmt.importingNode,
};

stylesheet.statements.splice(i, 1, importStmt);
}
}
}
3 changes: 3 additions & 0 deletions plugin-packs/postcss-bundler/test/_tape.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const testCases = {
'conditional-layer-before-external': {
message: 'correctly handles conditional stylesheets containing layer statements before external resources',
},
'relative-before-external': {
message: 'correctly handles stylesheets before external resources',
},
'does-not-exist-1': {
message: 'throws on files that don\'t exist',
exception: /Failed to find 'imports\/does-not-exist.css'/,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* imports/layer-before-external.css */

@import 'data:text/css;base64,QGxheWVyIHJlc2V0LCBib290c3RyYXA=' (min-width: 300px);
@import "data:text/css;base64,QGxheWVyIHJlc2V0LCBib290c3RyYXA=" (min-width: 300px);

@import 'data:text/css;base64,QGltcG9ydCB1cmwoJ2h0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vYm9vdHN0cmFwQDUuMy4wL2Rpc3QvY3NzL2Jvb3RzdHJhcC5jc3MnKSAobWluLXdpZHRoOiAzMDBweCk=' layer(bootstrap);
@import "data:text/css;base64,QGltcG9ydCB1cmwoJ2h0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vYm9vdHN0cmFwQDUuMy4wL2Rpc3QvY3NzL2Jvb3RzdHJhcC5jc3MnKSAobWluLXdpZHRoOiAzMDBweCk=" layer(bootstrap);

@media (min-width: 300px){

Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSAobWluLXdpZHRoOiAxcHgp' (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSAobWluLXdpZHRoOiAxcHgp" (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/* ./a.css */
@import url("http://localhost:8080/green.css") not print and (min-width: 1px);
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgbm90IHByaW50IGFuZCAobWluLXdpZHRoOiAxcHgp' not screen and (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgbm90IHByaW50IGFuZCAobWluLXdpZHRoOiAxcHgp" not screen and (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
@import url("http://localhost:8080/green.css");
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgc2NyZWVuIGFuZCAobm90IChtaW4td2lkdGg6IDFweCkp' not print and (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgc2NyZWVuIGFuZCAobm90IChtaW4td2lkdGg6IDFweCkp" not print and (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=' not screen and (max-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=" not screen and (max-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* ./a.css */
/* ./b.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=' not print and (min-color: 1);
@import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0IxY213b0ltaDBkSEE2THk5c2IyTmhiR2h2YzNRNk9EQTRNQzl5WldRdVkzTnpJaWtnYm05MElIQnlhVzUwSUdGdVpDQW9iV2x1TFhkcFpIUm9PaUF4Y0hncCcgbm90IHNjcmVlbiBhbmQgKG1pbi1oZWlnaHQ6IDFweCk=' not print and (min-color: 1);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=" not print and (min-color: 1);
@import "data:text/css;base64,QGltcG9ydCAiZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0IxY213b0ltaDBkSEE2THk5c2IyTmhiR2h2YzNRNk9EQTRNQzl5WldRdVkzTnpJaWtnYm05MElIQnlhVzUwSUdGdVpDQW9iV2x1TFhkcFpIUm9PaUF4Y0hncCIgbm90IHNjcmVlbiBhbmQgKG1pbi1oZWlnaHQ6IDFweCk=" not print and (min-color: 1);
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=' screen and (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=" screen and (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=' all and (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=" all and (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=' not print and (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=" not print and (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* ./a.css */
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=' all and (min-height: 1px);
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=" all and (min-height: 1px);
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* ./a.css */
@import 'data:text/css;base64,QGxheWVyIGIsIGE=' print;
@import "data:text/css;base64,QGxheWVyIGIsIGE=" print;
/* ./b.css */
/* a comment */
@import 'data:text/css;base64,QGxheWVyIGEsIGI=' screen;
@import "data:text/css;base64,QGxheWVyIGEsIGI=" screen;
@import url("http://localhost:8080/green.css") screen;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/* ./a.css */
.box {
background-color: red;
}
@import "data:text/css;base64,LmJveCB7CgliYWNrZ3JvdW5kLWNvbG9yOiByZWQ7Cn0=";
@import url("./a.css?background-color=green");
Original file line number Diff line number Diff line change
@@ -1,17 +1,3 @@
/* a.css */

.box {
animation: BOX;
animation-duration: 0s;
animation-fill-mode: both;
}

@keyframes BOX {

0%,
100% {
background-color: red;
}
}

@import "data:text/css;base64,CgouYm94IHsKCWFuaW1hdGlvbjogQk9YOwoJYW5pbWF0aW9uLWR1cmF0aW9uOiAwczsKCWFuaW1hdGlvbi1maWxsLW1vZGU6IGJvdGg7Cn0KCkBrZXlmcmFtZXMgQk9YIHsKCgkwJSwKCTEwMCUgewoJCWJhY2tncm91bmQtY29sb3I6IHJlZDsKCX0KfQ==";
@import url("http://localhost:8080/b.css");
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@import "imports/basic.css";
@import "https://localhost:8080/does-not-exist.css";

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.