diff --git a/src/plugins/postcss-import-parser.js b/src/plugins/postcss-import-parser.js index 9a75b2ad1..7bb5e65c1 100644 --- a/src/plugins/postcss-import-parser.js +++ b/src/plugins/postcss-import-parser.js @@ -118,15 +118,40 @@ function parseNode(atRule, key) { throw error; } - const mediaNodes = paramsNodes.slice(1); + const additionalNodes = paramsNodes.slice(1); + + let supports; + let layer; let media; - if (mediaNodes.length > 0) { - media = valueParser.stringify(mediaNodes).trim().toLowerCase(); + if (additionalNodes.length > 0) { + let nodes = []; + + for (const node of additionalNodes) { + nodes.push(node); + + if ( + (node.type === "function" && node.value.toLowerCase() === "layer") || + (node.type === "word" && node.value.toLowerCase() === "layer") + ) { + layer = valueParser.stringify(nodes).trim().toLowerCase(); + nodes = []; + } else if ( + node.type === "function" && + node.value.toLowerCase() === "supports" + ) { + supports = valueParser.stringify(nodes).trim().toLowerCase(); + nodes = []; + } + } + + if (nodes.length > 0) { + media = valueParser.stringify(nodes).trim().toLowerCase(); + } } // eslint-disable-next-line consistent-return - return { atRule, prefix, url, media, isRequestable }; + return { atRule, prefix, url, layer, supports, media, isRequestable }; } const plugin = (options = {}) => { @@ -160,11 +185,23 @@ const plugin = (options = {}) => { const resolvedAtRules = await Promise.all( parsedAtRules.map(async (parsedAtRule) => { - const { atRule, isRequestable, prefix, url, media } = - parsedAtRule; + const { + atRule, + isRequestable, + prefix, + url, + layer, + supports, + media, + } = parsedAtRule; if (options.filter) { - const needKeep = await options.filter(url, media); + const needKeep = await options.filter( + url, + media, + layer, + supports + ); if (!needKeep) { return; @@ -192,13 +229,20 @@ const plugin = (options = {}) => { atRule.remove(); // eslint-disable-next-line consistent-return - return { url: resolvedUrl, media, prefix, isRequestable }; + return { + url: resolvedUrl, + layer, + supports, + media, + prefix, + isRequestable, + }; } atRule.remove(); // eslint-disable-next-line consistent-return - return { url, media, prefix, isRequestable }; + return { url, layer, supports, media, prefix, isRequestable }; }) ); @@ -212,10 +256,11 @@ const plugin = (options = {}) => { continue; } - const { url, isRequestable, media } = resolvedAtRule; + const { url, isRequestable, layer, supports, media } = + resolvedAtRule; if (!isRequestable) { - options.api.push({ url, media, index }); + options.api.push({ url, layer, supports, media, index }); // eslint-disable-next-line no-continue continue; @@ -237,7 +282,7 @@ const plugin = (options = {}) => { }); } - options.api.push({ importName, media, index }); + options.api.push({ importName, layer, supports, media, index }); } }, }; diff --git a/src/runtime/api.js b/src/runtime/api.js index 1888424c4..5fa4caf62 100644 --- a/src/runtime/api.js +++ b/src/runtime/api.js @@ -10,10 +10,32 @@ module.exports = function (cssWithMappingToString) { // return the list of modules as css string list.toString = function toString() { return this.map((item) => { - const content = cssWithMappingToString(item); + let content = ""; + + if (item[4]) { + content += `@${item[4]} {`; + } + + if (item[3]) { + content += `@${item[3]} {`; + } + + if (item[2]) { + content += `@media ${item[2]} {`; + } + + content += cssWithMappingToString(item); if (item[2]) { - return `@media ${item[2]} {${content}}`; + content += "}"; + } + + if (item[3]) { + content += "}"; + } + + if (item[4]) { + content += "}"; } return content; @@ -22,7 +44,7 @@ module.exports = function (cssWithMappingToString) { // import a list of modules into the list // eslint-disable-next-line func-names - list.i = function (modules, mediaQuery, dedupe) { + list.i = function (modules, mediaQueryList, dedupe, layer, supports) { if (typeof modules === "string") { // eslint-disable-next-line no-param-reassign modules = [[null, modules, ""]]; @@ -49,11 +71,27 @@ module.exports = function (cssWithMappingToString) { continue; } - if (mediaQuery) { + if (mediaQueryList) { if (!item[2]) { - item[2] = mediaQuery; + item[2] = mediaQueryList; + } else { + item[2] = `${mediaQueryList} and ${item[2]}`; + } + } + + if (layer) { + if (!item[3]) { + item[3] = layer; + } else { + item[3] = `${layer} and ${item[3]}`; + } + } + + if (supports) { + if (!item[4]) { + item[4] = supports; } else { - item[2] = `${mediaQuery} and ${item[2]}`; + item[4] = `${supports} and ${item[4]}`; } } diff --git a/src/utils.js b/src/utils.js index 22a45fccc..2cb8bdf6e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -921,6 +921,32 @@ function normalizeSourceMapForRuntime(map, loaderContext) { return JSON.stringify(resultMap); } +function printParams(media, dedupe, supports, layer) { + let result = ""; + + if (layer) { + result = `, ${JSON.stringify(layer)}`; + } + + if (supports) { + result = `, ${JSON.stringify(supports)}${result}`; + } + + if (dedupe) { + result = `, true${result}`; + } else if (result.length > 0) { + result = `, false${result}`; + } + + if (media) { + result = `${JSON.stringify(media)}${result}`; + } else if (result.length > 0) { + result = `""${result}`; + } + + return result; +} + function getModuleCode(result, api, replacements, options, loaderContext) { if (options.modules.exportOnlyLocals === true) { return ""; @@ -939,15 +965,22 @@ function getModuleCode(result, api, replacements, options, loaderContext) { });\n`; for (const item of api) { - const { url, media, dedupe } = item; - - beforeCode += url - ? `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify( - `@import url(${url});` - )}${media ? `, ${JSON.stringify(media)}` : ""}]);\n` - : `___CSS_LOADER_EXPORT___.i(${item.importName}${ - media ? `, ${JSON.stringify(media)}` : dedupe ? ', ""' : "" - }${dedupe ? ", true" : ""});\n`; + const { url, layer, supports, media, dedupe } = item; + + if (url) { + // eslint-disable-next-line no-undefined + const printedParam = printParams(media, undefined, supports, layer); + + beforeCode += `___CSS_LOADER_EXPORT___.push([module.id, ${JSON.stringify( + `@import url(${url});` + )}${printedParam.length > 0 ? `, ${printedParam}` : ""}]);\n`; + } else { + const printedParam = printParams(media, dedupe, supports, layer); + + beforeCode += `___CSS_LOADER_EXPORT___.i(${item.importName}${ + printedParam.length > 0 ? `, ${printedParam}` : "" + });\n`; + } } for (const item of replacements) { diff --git a/test/fixtures/import/import.css b/test/fixtures/import/import.css index 6083012ed..da96ebe63 100644 --- a/test/fixtures/import/import.css +++ b/test/fixtures/import/import.css @@ -7,7 +7,7 @@ @import url( test.css); @import url( test.css ); @import url( -test.css + test.css ); @import url(); @import url(''); @@ -53,7 +53,7 @@ test.css } .foo { -@import 'path.css'; + @import 'path.css'; } @import url('./relative.css'); diff --git a/test/fixtures/import/import.js b/test/fixtures/import/import.js index 1d033ab23..0b5140a27 100644 --- a/test/fixtures/import/import.js +++ b/test/fixtures/import/import.js @@ -1,5 +1,5 @@ import css from './import.css'; -__export__ = css; +__export__ = css.toString(); export default css; diff --git a/test/import-option.test.js b/test/import-option.test.js index 69f826906..81546173c 100644 --- a/test/import-option.test.js +++ b/test/import-option.test.js @@ -13,7 +13,7 @@ import { } from "./helpers/index"; describe('"import" option', () => { - it("should work when not specified", async () => { + it.only("should work when not specified", async () => { const compiler = getCompiler("./import/import.js"); const stats = await compile(compiler);