Skip to content

Commit e7d2cfa

Browse files
authored
Fix bundling in more cases for @import statements that link to external resources (#1620)
1 parent 62b3f3f commit e7d2cfa

File tree

24 files changed

+116
-107
lines changed

24 files changed

+116
-107
lines changed

plugin-packs/postcss-bundler/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes to PostCSS Bundler
22

3+
### Unreleased (patch)
4+
5+
- Fix bundling in more cases for `@import` statements that link to external resources
6+
37
### 2.0.7
48

59
_May 27, 2025_

plugin-packs/postcss-bundler/dist/index.cjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

plugin-packs/postcss-bundler/dist/index.mjs

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

plugin-packs/postcss-bundler/src/postcss-import/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { postProcess } from './lib/post-process';
77
const creator: PluginCreator<never> = () => {
88
return {
99
postcssPlugin: 'postcss-bundler',
10-
async Once(styles, { result, atRule, postcss }): Promise<void> {
10+
async Once(styles, { result, atRule, root, postcss }): Promise<void> {
1111
const bundle = await parseStyles(
1212
result,
1313
styles,
@@ -17,7 +17,7 @@ const creator: PluginCreator<never> = () => {
1717
postcss,
1818
);
1919

20-
postProcess(bundle, atRule);
20+
postProcess(bundle, atRule, root);
2121

2222
applyConditions(bundle, atRule);
2323
applyStyles(bundle, styles);

plugin-packs/postcss-bundler/src/postcss-import/lib/base64-encoded-import.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export function base64EncodedConditionalImport(prelude: string, conditions: Arra
2121
)}`;
2222

2323
for (const condition of conditions) {
24-
params = `'data:text/css;base64,${Buffer.from(
24+
params = `"data:text/css;base64,${Buffer.from(
2525
`@import ${params}`,
26-
).toString('base64')}' ${formatImportPrelude(
26+
).toString('base64')}" ${formatImportPrelude(
2727
condition.layer,
2828
condition.media,
2929
condition.supports,
Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,105 @@
1-
import type { AtRule, AtRuleProps } from 'postcss';
1+
import type { AtRule, AtRuleProps, Root, RootProps } from 'postcss';
22
import type { ImportStatement, NodesStatement, Stylesheet} from './statement';
3-
import { isImportStatement, isNodesStatement, isPreImportStatement } from './statement';
3+
import { isImportStatement, isNodesStatement, isPreImportStatement, isWarning } from './statement';
44

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

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

1312
if (isImportStatement(stmt)) {
14-
indexOfFirstImport = i;
15-
if (indexOfFirstImport !== -1 && indexOfFirstPreImport !== -1 && indexOfFirstMeaningfulNode !== -1) {
16-
break;
13+
if (indexOfFirstImport === -1) {
14+
indexOfFirstImport = i;
1715
}
1816

17+
indexOfLastImport = i;
1918
continue;
2019
}
20+
}
2121

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

25+
if (isImportStatement(stmt)) {
2826
continue;
2927
}
3028

31-
if (isNodesStatement(stmt)) {
32-
for (let j = 0; j < stmt.nodes.length; j++) {
33-
const node = stmt.nodes[j];
34-
if (node.type === 'comment') {
35-
continue;
36-
}
29+
if (isWarning(stmt)) {
30+
continue;
31+
}
3732

38-
indexOfFirstMeaningfulNode = i;
39-
}
33+
if (isNodesStatement(stmt) && !stmt.importingNode) {
34+
continue;
35+
}
36+
37+
if (isPreImportStatement(stmt)) {
38+
if (i < indexOfLastImport) {
39+
const params = 'data:text/css;base64,' + Buffer.from(stmt.node.toString()).toString('base64');
4040

41-
if (indexOfFirstImport !== -1 && indexOfFirstPreImport !== -1 && indexOfFirstMeaningfulNode !== -1) {
42-
break;
41+
const importStmt: ImportStatement = {
42+
type: 'import',
43+
uri: params,
44+
fullUri: '"' + params + '"',
45+
node: atRule({
46+
name: 'import',
47+
params: '"' + params + '"',
48+
source: stmt.node.source,
49+
}),
50+
conditions: stmt.conditions,
51+
from: stmt.from,
52+
importingNode: stmt.importingNode,
53+
};
54+
55+
stylesheet.statements.splice(i, 1, importStmt);
56+
} else {
57+
const nodesStmt: NodesStatement = {
58+
type: 'nodes',
59+
nodes: [stmt.node],
60+
conditions: stmt.conditions,
61+
from: stmt.from,
62+
importingNode: stmt.importingNode,
63+
};
64+
65+
stylesheet.statements.splice(i, 1, nodesStmt);
4366
}
4467

4568
continue;
4669
}
47-
}
4870

49-
if (indexOfFirstPreImport !== -1) {
50-
for (let i = 0; i < stylesheet.statements.length; i++) {
51-
const stmt = stylesheet.statements[i];
52-
53-
if (isPreImportStatement(stmt)) {
54-
if (
55-
i < indexOfFirstImport &&
56-
(
57-
i < indexOfFirstMeaningfulNode ||
58-
indexOfFirstMeaningfulNode === -1
59-
)
60-
) {
61-
const params = 'data:text/css;base64,' + Buffer.from(stmt.node.toString()).toString('base64');
62-
63-
const importStmt: ImportStatement = {
64-
type: 'import',
65-
uri: params,
66-
fullUri: '\'' + params + '\'',
67-
node: atRule({
68-
name: 'import',
69-
params: '\'' + params + '\'',
70-
source: stmt.node.source,
71-
}),
72-
conditions: stmt.conditions,
73-
from: stmt.from,
74-
importingNode: stmt.importingNode,
75-
};
76-
77-
stylesheet.statements.splice(i, 1, importStmt);
78-
} else {
79-
const nodesStmt: NodesStatement = {
80-
type: 'nodes',
81-
nodes: [stmt.node],
82-
conditions: stmt.conditions,
83-
from: stmt.from,
84-
importingNode: stmt.importingNode,
85-
};
86-
87-
stylesheet.statements.splice(i, 1, nodesStmt);
88-
}
89-
}
71+
if (i < indexOfFirstImport && stmt.nodes.every((x) => x.type === 'atrule' && !x.nodes)) {
72+
continue;
73+
}
74+
75+
if (i < indexOfLastImport && (stmt.nodes.every((x) => x.type === 'comment'))) {
76+
continue;
77+
}
78+
79+
if (i < indexOfLastImport) {
80+
const dummyRoot = root();
81+
stmt.nodes.forEach((node) => {
82+
node.parent = undefined;
83+
dummyRoot.append(node);
84+
});
85+
86+
const params = 'data:text/css;base64,' + Buffer.from(dummyRoot.toString()).toString('base64');
87+
88+
const importStmt: ImportStatement = {
89+
type: 'import',
90+
uri: params,
91+
fullUri: '"' + params + '"',
92+
node: atRule({
93+
name: 'import',
94+
params: '"' + params + '"',
95+
source: stmt.importingNode?.source ?? stmt.nodes[0]?.source,
96+
}),
97+
conditions: stmt.conditions,
98+
from: stmt.from,
99+
importingNode: stmt.importingNode,
100+
};
101+
102+
stylesheet.statements.splice(i, 1, importStmt);
90103
}
91104
}
92105
}

plugin-packs/postcss-bundler/test/_tape.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const testCases = {
1818
'conditional-layer-before-external': {
1919
message: 'correctly handles conditional stylesheets containing layer statements before external resources',
2020
},
21+
'relative-before-external': {
22+
message: 'correctly handles stylesheets before external resources',
23+
},
2124
'does-not-exist-1': {
2225
message: 'throws on files that don\'t exist',
2326
exception: /Failed to find 'imports\/does-not-exist.css'/,

plugin-packs/postcss-bundler/test/conditional-layer-before-external.expect.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* imports/layer-before-external.css */
22

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

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

77
@media (min-width: 300px){
88

Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSAobWluLXdpZHRoOiAxcHgp' (min-height: 1px);
2+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSAobWluLXdpZHRoOiAxcHgp" (min-height: 1px);
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
/* ./a.css */
22
@import url("http://localhost:8080/green.css") not print and (min-width: 1px);
3-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgbm90IHByaW50IGFuZCAobWluLXdpZHRoOiAxcHgp' not screen and (min-height: 1px);
3+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgbm90IHByaW50IGFuZCAobWluLXdpZHRoOiAxcHgp" not screen and (min-height: 1px);
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
@import url("http://localhost:8080/green.css");
22
/* ./a.css */
3-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgc2NyZWVuIGFuZCAobm90IChtaW4td2lkdGg6IDFweCkp' not print and (min-height: 1px);
3+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9yZWQuY3NzIikgc2NyZWVuIGFuZCAobm90IChtaW4td2lkdGg6IDFweCkp" not print and (min-height: 1px);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=' not screen and (max-height: 1px);
2+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=" not screen and (max-height: 1px);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/* ./a.css */
22
/* ./b.css */
3-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=' not print and (min-color: 1);
4-
@import 'data:text/css;base64,QGltcG9ydCAnZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0IxY213b0ltaDBkSEE2THk5c2IyTmhiR2h2YzNRNk9EQTRNQzl5WldRdVkzTnpJaWtnYm05MElIQnlhVzUwSUdGdVpDQW9iV2x1TFhkcFpIUm9PaUF4Y0hncCcgbm90IHNjcmVlbiBhbmQgKG1pbi1oZWlnaHQ6IDFweCk=' not print and (min-color: 1);
3+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=" not print and (min-color: 1);
4+
@import "data:text/css;base64,QGltcG9ydCAiZGF0YTp0ZXh0L2NzcztiYXNlNjQsUUdsdGNHOXlkQ0IxY213b0ltaDBkSEE2THk5c2IyTmhiR2h2YzNRNk9EQTRNQzl5WldRdVkzTnpJaWtnYm05MElIQnlhVzUwSUdGdVpDQW9iV2x1TFhkcFpIUm9PaUF4Y0hncCIgbm90IHNjcmVlbiBhbmQgKG1pbi1oZWlnaHQ6IDFweCk=" not print and (min-color: 1);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=' screen and (min-height: 1px);
2+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=" screen and (min-height: 1px);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=' all and (min-height: 1px);
2+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=" all and (min-height: 1px);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=' not print and (min-height: 1px);
2+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDFweCk=" not print and (min-height: 1px);
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=' all and (min-height: 1px);
2+
@import "data:text/css;base64,QGltcG9ydCB1cmwoImh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9ncmVlbi5jc3MiKSBub3QgcHJpbnQgYW5kIChtaW4td2lkdGg6IDFweCk=" all and (min-height: 1px);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* ./a.css */
2-
@import 'data:text/css;base64,QGxheWVyIGIsIGE=' print;
2+
@import "data:text/css;base64,QGxheWVyIGIsIGE=" print;
33
/* ./b.css */
44
/* a comment */
5-
@import 'data:text/css;base64,QGxheWVyIGEsIGI=' screen;
5+
@import "data:text/css;base64,QGxheWVyIGEsIGI=" screen;
66
@import url("http://localhost:8080/green.css") screen;

plugin-packs/postcss-bundler/test/css-import-tests/002-sub-features/004-at-supports/006/style.expect.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugin-packs/postcss-bundler/test/css-import-tests/002-sub-features/005-at-scope/006/style.expect.css

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
/* ./a.css */
2-
.box {
3-
background-color: red;
4-
}
2+
@import "data:text/css;base64,LmJveCB7CgliYWNrZ3JvdW5kLWNvbG9yOiByZWQ7Cn0=";
53
@import url("./a.css?background-color=green");
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
11
/* a.css */
2-
3-
.box {
4-
animation: BOX;
5-
animation-duration: 0s;
6-
animation-fill-mode: both;
7-
}
8-
9-
@keyframes BOX {
10-
11-
0%,
12-
100% {
13-
background-color: red;
14-
}
15-
}
16-
2+
@import "data:text/css;base64,CgouYm94IHsKCWFuaW1hdGlvbjogQk9YOwoJYW5pbWF0aW9uLWR1cmF0aW9uOiAwczsKCWFuaW1hdGlvbi1maWxsLW1vZGU6IGJvdGg7Cn0KCkBrZXlmcmFtZXMgQk9YIHsKCgkwJSwKCTEwMCUgewoJCWJhY2tncm91bmQtY29sb3I6IHJlZDsKCX0KfQ==";
173
@import url("http://localhost:8080/b.css");
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@import "imports/basic.css";
2+
@import "https://localhost:8080/does-not-exist.css";

plugin-packs/postcss-bundler/test/relative-before-external.expect.css

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)