Skip to content

Commit 1e4199f

Browse files
committed
Fix bundling in more cases for @import statements that link to external resources
1 parent 62b3f3f commit 1e4199f

24 files changed

Lines changed: 116 additions & 107 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
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);
Lines changed: 1 addition & 1 deletion
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);

0 commit comments

Comments
 (0)