Skip to content

Commit bce2c17

Browse files
feat: supported supports() and layer() and fix multiple @media merging in @import at-rule (#1377)
1 parent cf3a3a7 commit bce2c17

27 files changed

+1251
-1874
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module.exports = {
1212
"no-param-reassign": "off",
1313
"no-continue": "off",
1414
"no-underscore-dangle": "off",
15+
"no-undefined": "off",
1516
},
1617
},
1718
],

src/index.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default async function loader(content, map, meta) {
7171
context: this.context,
7272
rootContext: this.rootContext,
7373
resourcePath: this.resourcePath,
74-
filter: getFilter(options.import.filter, this.resourcePath),
74+
filter: options.import.filter,
7575
resolver,
7676
urlHandler: (url) =>
7777
stringifyRequest(
@@ -205,13 +205,11 @@ export default async function loader(content, map, meta) {
205205

206206
if (options.sourceMap) {
207207
imports.unshift({
208-
type: "api_sourcemap_import",
209208
importName: "___CSS_LOADER_API_SOURCEMAP_IMPORT___",
210-
url: stringifyRequest(this, require.resolve("./runtime/noSourceMaps")),
209+
url: stringifyRequest(this, require.resolve("./runtime/sourceMaps")),
211210
});
212211
} else {
213212
imports.unshift({
214-
type: "api_sourcemap_import",
215213
importName: "___CSS_LOADER_API_NO_SOURCEMAP_IMPORT___",
216214
url: stringifyRequest(this, require.resolve("./runtime/noSourceMaps")),
217215
});

src/plugins/postcss-import-parser.js

+72-12
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,54 @@ function parseNode(atRule, key) {
118118
throw error;
119119
}
120120

121-
const mediaNodes = paramsNodes.slice(1);
121+
const additionalNodes = paramsNodes.slice(1);
122+
123+
let supports;
124+
let layer;
122125
let media;
123126

124-
if (mediaNodes.length > 0) {
125-
media = valueParser.stringify(mediaNodes).trim().toLowerCase();
127+
if (additionalNodes.length > 0) {
128+
let nodes = [];
129+
130+
for (const node of additionalNodes) {
131+
nodes.push(node);
132+
133+
const isLayerFunction =
134+
node.type === "function" && node.value.toLowerCase() === "layer";
135+
const isLayerWord =
136+
node.type === "word" && node.value.toLowerCase() === "layer";
137+
138+
if (isLayerFunction || isLayerWord) {
139+
if (isLayerFunction) {
140+
nodes.splice(nodes.length - 1, 1, ...node.nodes);
141+
} else {
142+
nodes.splice(nodes.length - 1, 1, {
143+
type: "string",
144+
value: "",
145+
unclosed: false,
146+
});
147+
}
148+
149+
layer = valueParser.stringify(nodes).trim().toLowerCase();
150+
nodes = [];
151+
} else if (
152+
node.type === "function" &&
153+
node.value.toLowerCase() === "supports"
154+
) {
155+
nodes.splice(nodes.length - 1, 1, ...node.nodes);
156+
157+
supports = valueParser.stringify(nodes).trim().toLowerCase();
158+
nodes = [];
159+
}
160+
}
161+
162+
if (nodes.length > 0) {
163+
media = valueParser.stringify(nodes).trim().toLowerCase();
164+
}
126165
}
127166

128167
// eslint-disable-next-line consistent-return
129-
return { atRule, prefix, url, media, isRequestable };
168+
return { atRule, prefix, url, layer, supports, media, isRequestable };
130169
}
131170

132171
const plugin = (options = {}) => {
@@ -160,11 +199,24 @@ const plugin = (options = {}) => {
160199

161200
const resolvedAtRules = await Promise.all(
162201
parsedAtRules.map(async (parsedAtRule) => {
163-
const { atRule, isRequestable, prefix, url, media } =
164-
parsedAtRule;
202+
const {
203+
atRule,
204+
isRequestable,
205+
prefix,
206+
url,
207+
layer,
208+
supports,
209+
media,
210+
} = parsedAtRule;
165211

166212
if (options.filter) {
167-
const needKeep = await options.filter(url, media);
213+
const needKeep = await options.filter(
214+
url,
215+
media,
216+
options.resourcePath,
217+
supports,
218+
layer
219+
);
168220

169221
if (!needKeep) {
170222
return;
@@ -192,13 +244,20 @@ const plugin = (options = {}) => {
192244
atRule.remove();
193245

194246
// eslint-disable-next-line consistent-return
195-
return { url: resolvedUrl, media, prefix, isRequestable };
247+
return {
248+
url: resolvedUrl,
249+
layer,
250+
supports,
251+
media,
252+
prefix,
253+
isRequestable,
254+
};
196255
}
197256

198257
atRule.remove();
199258

200259
// eslint-disable-next-line consistent-return
201-
return { url, media, prefix, isRequestable };
260+
return { url, layer, supports, media, prefix, isRequestable };
202261
})
203262
);
204263

@@ -212,10 +271,11 @@ const plugin = (options = {}) => {
212271
continue;
213272
}
214273

215-
const { url, isRequestable, media } = resolvedAtRule;
274+
const { url, isRequestable, layer, supports, media } =
275+
resolvedAtRule;
216276

217277
if (!isRequestable) {
218-
options.api.push({ url, media, index });
278+
options.api.push({ url, layer, supports, media, index });
219279

220280
// eslint-disable-next-line no-continue
221281
continue;
@@ -237,7 +297,7 @@ const plugin = (options = {}) => {
237297
});
238298
}
239299

240-
options.api.push({ importName, media, index });
300+
options.api.push({ importName, layer, supports, media, index });
241301
}
242302
},
243303
};

src/runtime/api.js

+52-7
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,44 @@ module.exports = (cssWithMappingToString) => {
88
// return the list of modules as css string
99
list.toString = function toString() {
1010
return this.map((item) => {
11-
const content = cssWithMappingToString(item);
11+
let content = "";
12+
13+
const needLayer = typeof item[5] !== "undefined";
14+
15+
if (item[4]) {
16+
content += `@supports (${item[4]}) {`;
17+
}
18+
19+
if (item[2]) {
20+
content += `@media ${item[2]} {`;
21+
}
22+
23+
if (needLayer) {
24+
content += `@layer${item[5].length > 0 ? ` ${item[5]}` : ""} {`;
25+
}
26+
27+
content += cssWithMappingToString(item);
28+
29+
if (needLayer) {
30+
content += "}";
31+
}
1232

1333
if (item[2]) {
14-
return `@media ${item[2]} {${content}}`;
34+
content += "}";
35+
}
36+
37+
if (item[4]) {
38+
content += "}";
1539
}
1640

1741
return content;
1842
}).join("");
1943
};
2044

2145
// import a list of modules into the list
22-
list.i = function i(modules, mediaQuery, dedupe) {
46+
list.i = function i(modules, media, dedupe, supports, layer) {
2347
if (typeof modules === "string") {
24-
modules = [[null, modules, ""]];
48+
modules = [[null, modules, undefined]];
2549
}
2650

2751
const alreadyImportedModules = {};
@@ -43,11 +67,32 @@ module.exports = (cssWithMappingToString) => {
4367
continue;
4468
}
4569

46-
if (mediaQuery) {
70+
if (typeof layer !== "undefined") {
71+
if (typeof item[5] === "undefined") {
72+
item[5] = layer;
73+
} else {
74+
item[1] = `@layer${item[5].length > 0 ? ` ${item[5]}` : ""} {${
75+
item[1]
76+
}}`;
77+
item[5] = layer;
78+
}
79+
}
80+
81+
if (media) {
4782
if (!item[2]) {
48-
item[2] = mediaQuery;
83+
item[2] = media;
84+
} else {
85+
item[1] = `@media ${item[2]} {${item[1]}}`;
86+
item[2] = media;
87+
}
88+
}
89+
90+
if (supports) {
91+
if (!item[4]) {
92+
item[4] = `${supports}`;
4993
} else {
50-
item[2] = `${mediaQuery} and ${item[2]}`;
94+
item[1] = `@supports (${item[4]}) {${item[1]}}`;
95+
item[4] = supports;
5196
}
5297
}
5398

src/utils.js

+51-9
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,34 @@ function normalizeSourceMapForRuntime(map, loaderContext) {
921921
return JSON.stringify(resultMap);
922922
}
923923

924+
function printParams(media, dedupe, supports, layer) {
925+
let result = "";
926+
927+
if (typeof layer !== "undefined") {
928+
result = `, ${JSON.stringify(layer)}`;
929+
}
930+
931+
if (typeof supports !== "undefined") {
932+
result = `, ${JSON.stringify(supports)}${result}`;
933+
} else if (result.length > 0) {
934+
result = `, undefined${result}`;
935+
}
936+
937+
if (dedupe) {
938+
result = `, true${result}`;
939+
} else if (result.length > 0) {
940+
result = `, false${result}`;
941+
}
942+
943+
if (media) {
944+
result = `${JSON.stringify(media)}${result}`;
945+
} else if (result.length > 0) {
946+
result = `""${result}`;
947+
}
948+
949+
return result;
950+
}
951+
924952
function getModuleCode(result, api, replacements, options, loaderContext) {
925953
if (options.modules.exportOnlyLocals === true) {
926954
return "";
@@ -939,15 +967,22 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
939967
});\n`;
940968

941969
for (const item of api) {
942-
const { url, media, dedupe } = item;
943-
944-
beforeCode += url
945-
? `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify(
946-
`@import url(${url});`
947-
)}${media ? `, ${JSON.stringify(media)}` : ""}]);\n`
948-
: `___CSS_LOADER_EXPORT___.i(${item.importName}${
949-
media ? `, ${JSON.stringify(media)}` : dedupe ? ', ""' : ""
950-
}${dedupe ? ", true" : ""});\n`;
970+
const { url, layer, supports, media, dedupe } = item;
971+
972+
if (url) {
973+
// eslint-disable-next-line no-undefined
974+
const printedParam = printParams(media, undefined, supports, layer);
975+
976+
beforeCode += `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify(
977+
`@import url(${url});`
978+
)}${printedParam.length > 0 ? `, ${printedParam}` : ""}]);\n`;
979+
} else {
980+
const printedParam = printParams(media, dedupe, supports, layer);
981+
982+
beforeCode += `___CSS_LOADER_EXPORT___.i(${item.importName}${
983+
printedParam.length > 0 ? `, ${printedParam}` : ""
984+
});\n`;
985+
}
951986
}
952987

953988
for (const item of replacements) {
@@ -980,6 +1015,13 @@ function getModuleCode(result, api, replacements, options, loaderContext) {
9801015
}
9811016
}
9821017

1018+
// Indexes description:
1019+
// 0 - module id
1020+
// 1 - CSS code
1021+
// 2 - media
1022+
// 3 - source map
1023+
// 4 - supports
1024+
// 5 - layer
9831025
return `${beforeCode}// Module\n___CSS_LOADER_EXPORT___.push([module.id, ${code}, ""${sourceMapValue}]);\n`;
9841026
}
9851027

0 commit comments

Comments
 (0)