diff --git a/index.js b/index.js index cc1237da..3e98cc77 100755 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ const path = require("path") // internal tooling -const applyMedia = require("./lib/apply-media") +const applyConditions = require("./lib/apply-conditions") const applyRaws = require("./lib/apply-raws") const applyStyles = require("./lib/apply-styles") const loadContent = require("./lib/load-content") @@ -61,12 +61,12 @@ function AtImport(options) { options, state, [], - [], + "", postcss ) applyRaws(bundle) - applyMedia(bundle, options, state, atRule) + applyConditions(bundle, options, state, atRule) applyStyles(bundle, styles) }, } diff --git a/lib/apply-conditions.js b/lib/apply-conditions.js new file mode 100644 index 00000000..cbcd98bb --- /dev/null +++ b/lib/apply-conditions.js @@ -0,0 +1,129 @@ +"use strict" + +const assignLayerNames = require("./assign-layer-names") +const joinLayer = require("./join-layer") +const joinMedia = require("./join-media") +const joinSupports = require("./join-supports") + +module.exports = function (bundle, options, state, atRule) { + bundle.forEach(stmt => { + if (!stmt.conditions.length || stmt.type === "charset") { + return + } + + if (1 < stmt.conditions.filter(x => x.layer.length > 0).length) { + for (const condition of stmt.conditions) { + if (condition.layer.length > 0) { + assignLayerNames(condition.layer, stmt.node, state, options) + } + } + } + + if (stmt.type === "import") { + const parts = [stmt.fullUri] + + let media = [] + let supports = [] + let layer = [] + + for (const condition of stmt.conditions) { + media = joinMedia(media, condition.media) + supports = joinSupports(supports, condition.supports) + layer = joinLayer(layer, condition.layer) + } + + if (layer.length) { + const layerName = layer.join(".") + + let layerParams = "layer" + if (layerName) { + layerParams = `layer(${layerName})` + } + + parts.push(layerParams) + } + + if (supports.length) { + if (supports.length === 1) { + parts.push(`supports(${supports[0]})`) + } else { + parts.push(`supports(${supports.map(x => `(${x})`).join(" and ")})`) + } + } + + if (media.length) { + parts.push(media.join(", ")) + } + + stmt.node.params = parts.join(" ") + + return + } + + const { nodes } = stmt + const { parent } = nodes[0] + + const atRules = [] + + // Convert conditions to at-rules + for (const condition of stmt.conditions) { + if (condition.media.length > 0) { + const mediaNode = atRule({ + name: "media", + params: condition.media.join(", "), + source: parent.source, + }) + + atRules.push(mediaNode) + } + + if (condition.supports.length > 0) { + const supportsNode = atRule({ + name: "supports", + params: + condition.supports.length === 1 + ? `(${condition.supports[0]})` + : condition.supports.map(x => `(${x})`).join(" and "), + source: parent.source, + }) + + atRules.push(supportsNode) + } + + if (condition.layer.length > 0) { + const layerNode = atRule({ + name: "layer", + params: condition.layer.join("."), + source: parent.source, + }) + + atRules.push(layerNode) + } + } + + // Add nodes to AST + for (let i = 0; i < atRules.length - 1; i++) { + atRules[i].append(atRules[i + 1]) + } + + const innerAtRule = atRules[atRules.length - 1] + const outerAtRule = atRules[0] + + parent.insertBefore(nodes[0], outerAtRule) + + // remove nodes + nodes.forEach(node => { + node.parent = undefined + }) + + // better output + nodes[0].raws.before = nodes[0].raws.before || "\n" + + // wrap new rules with media query and/or layer at rule + innerAtRule.append(nodes) + + stmt.type = "nodes" + stmt.nodes = [outerAtRule] + delete stmt.node + }) +} diff --git a/lib/apply-media.js b/lib/apply-media.js deleted file mode 100644 index 7d4d1d69..00000000 --- a/lib/apply-media.js +++ /dev/null @@ -1,114 +0,0 @@ -"use strict" - -const assignLayerNames = require("./assign-layer-names") - -module.exports = function (bundle, options, state, atRule) { - bundle.forEach(stmt => { - if ((!stmt.media.length && !stmt.layer.length) || stmt.type === "charset") { - return - } - - if (stmt.layer.length > 1) { - assignLayerNames(stmt.layer, stmt.node, state, options) - } - - if (stmt.type === "import") { - const parts = [stmt.fullUri] - - const media = stmt.media.join(", ") - - if (stmt.layer.length) { - const layerName = stmt.layer.join(".") - - let layerParams = "layer" - if (layerName) { - layerParams = `layer(${layerName})` - } - - parts.push(layerParams) - } - - if (media) { - parts.push(media) - } - - stmt.node.params = parts.join(" ") - } else if (stmt.type === "media") { - if (stmt.layer.length) { - const layerNode = atRule({ - name: "layer", - params: stmt.layer.join("."), - source: stmt.node.source, - }) - - if (stmt.parentMedia?.length) { - const mediaNode = atRule({ - name: "media", - params: stmt.parentMedia.join(", "), - source: stmt.node.source, - }) - - mediaNode.append(layerNode) - layerNode.append(stmt.node) - stmt.node = mediaNode - } else { - layerNode.append(stmt.node) - delete stmt.node - stmt.nodes = [layerNode] - stmt.type = "nodes" - } - } else { - stmt.node.params = stmt.media.join(", ") - } - } else { - const { nodes } = stmt - const { parent } = nodes[0] - - const atRules = [] - - if (stmt.media.length) { - const mediaNode = atRule({ - name: "media", - params: stmt.media.join(", "), - source: parent.source, - }) - - atRules.push(mediaNode) - } - - if (stmt.layer.length) { - const layerNode = atRule({ - name: "layer", - params: stmt.layer.join("."), - source: parent.source, - }) - - atRules.push(layerNode) - } - - for (let i = 1; i < atRules.length; i++) { - atRules[i - 1].append(atRules[i]) - } - - const innerAtRule = atRules[atRules.length - 1] - const outerAtRule = atRules[0] - - parent.insertBefore(nodes[0], outerAtRule) - - // remove nodes - nodes.forEach(node => { - node.parent = undefined - }) - - // better output - nodes[0].raws.before = nodes[0].raws.before || "\n" - - // wrap new rules with media query and/or layer at rule - innerAtRule.append(nodes) - - stmt.type = "media" - stmt.node = outerAtRule - delete stmt.nodes - } - }) -} diff --git a/lib/apply-styles.js b/lib/apply-styles.js index dc3a2560..3ba46ce1 100644 --- a/lib/apply-styles.js +++ b/lib/apply-styles.js @@ -5,7 +5,7 @@ module.exports = function (bundle, styles) { // Strip additional statements. bundle.forEach(stmt => { - if (["charset", "import", "media"].includes(stmt.type)) { + if (["charset", "import"].includes(stmt.type)) { stmt.node.parent = undefined styles.append(stmt.node) } else if (stmt.type === "nodes") { diff --git a/lib/check-for-cycles.js b/lib/check-for-cycles.js new file mode 100644 index 00000000..acef80cf --- /dev/null +++ b/lib/check-for-cycles.js @@ -0,0 +1,88 @@ +"use strict" + +// This is a modified version of toposort. +// Instead of throwing on cycles, it returns true. +function checkForCycles(edges) { + const nodes = uniqueNodes(edges) + + let cursor = nodes.length + const sorted = new Array(cursor) + const visited = {} + let i = cursor + const outgoingEdges = makeOutgoingEdges(edges) + const nodesHash = makeNodesHash(nodes) + + while (i--) { + if (visited[i]) { + continue + } + + if (visit(nodes[i], i, new Set())) { + return true + } + } + + return false + + function visit(node, i, predecessors) { + if (predecessors.has(node)) { + // Has a cycle. + return true + } + + if (visited[i]) { + return false + } + + visited[i] = true + + let outgoing = outgoingEdges.get(node) || new Set() + outgoing = Array.from(outgoing) + + if ((i = outgoing.length)) { + predecessors.add(node) + do { + const child = outgoing[--i] + if (visit(child, nodesHash.get(child), predecessors)) { + return true + } + } while (i) + predecessors.delete(node) + } + + sorted[--cursor] = node + + return false + } +} + +function uniqueNodes(arr) { + const res = new Set() + for (let i = 0, len = arr.length; i < len; i++) { + const edge = arr[i] + res.add(edge[0]) + res.add(edge[1]) + } + return Array.from(res) +} + +function makeOutgoingEdges(arr) { + const edges = new Map() + for (let i = 0, len = arr.length; i < len; i++) { + const edge = arr[i] + if (!edges.has(edge[0])) edges.set(edge[0], new Set()) + if (!edges.has(edge[1])) edges.set(edge[1], new Set()) + edges.get(edge[0]).add(edge[1]) + } + return edges +} + +function makeNodesHash(arr) { + const res = new Map() + for (let i = 0, len = arr.length; i < len; i++) { + res.set(arr[i], i) + } + return res +} + +module.exports = checkForCycles diff --git a/lib/data-url.js b/lib/data-url.js index a59c5fb5..69c4ce94 100644 --- a/lib/data-url.js +++ b/lib/data-url.js @@ -1,14 +1,26 @@ "use strict" -const dataURLRegexp = /^data:text\/css;base64,/i +const anyDataURLRegexp = /^data:text\/css(?:;(base64|plain))?,/i +const base64DataURLRegexp = /^data:text\/css;base64,/i +const plainDataURLRegexp = /^data:text\/css;plain,/i function isValid(url) { - return dataURLRegexp.test(url) + return anyDataURLRegexp.test(url) } function contents(url) { - // "data:text/css;base64,".length === 21 - return Buffer.from(url.slice(21), "base64").toString() + if (base64DataURLRegexp.test(url)) { + // "data:text/css;base64,".length === 21 + return Buffer.from(url.slice(21), "base64").toString() + } + + if (plainDataURLRegexp.test(url)) { + // "data:text/css;plain,".length === 20 + return decodeURIComponent(url.slice(20)) + } + + // "data:text/css,".length === 14 + return decodeURIComponent(url.slice(14)) } module.exports = { diff --git a/lib/join-media.js b/lib/join-media.js index fcaaecd3..3afccdde 100644 --- a/lib/join-media.js +++ b/lib/join-media.js @@ -20,6 +20,8 @@ module.exports = function (parentMedia, childMedia) { } else { media.push(`${parentItem} and ${childItem}`) } + } else { + media.push(parentItem) } }) }) diff --git a/lib/join-supports.js b/lib/join-supports.js new file mode 100644 index 00000000..dde6cb4e --- /dev/null +++ b/lib/join-supports.js @@ -0,0 +1,9 @@ +"use strict" + +module.exports = function (parentSupports, childSupports) { + if (!parentSupports.length && childSupports.length) return childSupports + if (parentSupports.length && !childSupports.length) return parentSupports + if (!parentSupports.length && !childSupports.length) return [] + + return Array.from(new Set(parentSupports.concat(childSupports))) +} diff --git a/lib/parse-statements.js b/lib/parse-statements.js index 0c94e5a4..c1fd99a1 100644 --- a/lib/parse-statements.js +++ b/lib/parse-statements.js @@ -20,16 +20,17 @@ function split(params, start) { return list } -module.exports = function (result, styles) { +function parseStatements(result, styles, conditions, from) { const statements = [] let nodes = [] styles.each(node => { let stmt if (node.type === "atrule") { - if (node.name === "import") stmt = parseImport(result, node) - else if (node.name === "media") stmt = parseMedia(result, node) - else if (node.name === "charset") stmt = parseCharset(result, node) + if (node.name === "import") + stmt = parseImport(result, node, conditions, from) + else if (node.name === "charset") + stmt = parseCharset(result, node, conditions, from) } if (stmt) { @@ -37,8 +38,8 @@ module.exports = function (result, styles) { statements.push({ type: "nodes", nodes, - media: [], - layer: [], + conditions: [...conditions], + from, }) nodes = [] } @@ -50,25 +51,15 @@ module.exports = function (result, styles) { statements.push({ type: "nodes", nodes, - media: [], - layer: [], + conditions: [...conditions], + from, }) } return statements } -function parseMedia(result, atRule) { - const params = valueParser(atRule.params).nodes - return { - type: "media", - node: atRule, - media: split(params, 0), - layer: [], - } -} - -function parseCharset(result, atRule) { +function parseCharset(result, atRule, conditions, from) { if (atRule.prev()) { return result.warn("@charset must precede all other statements", { node: atRule, @@ -77,12 +68,12 @@ function parseCharset(result, atRule) { return { type: "charset", node: atRule, - media: [], - layer: [], + conditions: [...conditions], + from, } } -function parseImport(result, atRule) { +function parseImport(result, atRule, conditions, from) { let prev = atRule.prev() if (prev) { do { @@ -111,62 +102,105 @@ function parseImport(result, atRule) { } const params = valueParser(atRule.params).nodes + const stmt = { type: "import", + uri: "", + fullUri: "", node: atRule, - media: [], - layer: [], + conditions: [...conditions], + from, } - // prettier-ignore - if ( - !params.length || - ( - params[0].type !== "string" || - !params[0].value - ) && - ( - params[0].type !== "function" || - params[0].value !== "url" || - !params[0].nodes.length || - !params[0].nodes[0].value - ) - ) { - return result.warn(`Unable to find uri in '${ atRule.toString() }'`, { - node: atRule, - }) - } + let layer = [] + let media = [] + let supports = [] - if (params[0].type === "string") stmt.uri = params[0].value - else stmt.uri = params[0].nodes[0].value - stmt.fullUri = stringify(params[0]) + for (let i = 0; i < params.length; i++) { + const node = params[i] - let remainder = params - if (remainder.length > 2) { - if ( - (remainder[2].type === "word" || remainder[2].type === "function") && - remainder[2].value === "layer" - ) { - if (remainder[1].type !== "space") { - return result.warn("Invalid import layer statement", { node: atRule }) + if (node.type === "space" || node.type === "comment") continue + + if (node.type === "string") { + if (stmt.uri) { + return result.warn(`Multiple url's in '${atRule.toString()}'`, { + node: atRule, + }) } - if (remainder[2].nodes) { - stmt.layer = [stringify(remainder[2].nodes)] + if (!node.value) { + return result.warn(`Unable to find uri in '${atRule.toString()}'`, { + node: atRule, + }) + } + + stmt.uri = node.value + stmt.fullUri = stringify(node) + continue + } + + if (node.type === "function" && /^url$/i.test(node.value)) { + if (stmt.uri) { + return result.warn(`Multiple url's in '${atRule.toString()}'`, { + node: atRule, + }) + } + + if (!node.nodes?.[0]?.value) { + return result.warn(`Unable to find uri in '${atRule.toString()}'`, { + node: atRule, + }) + } + + stmt.uri = node.nodes[0].value + stmt.fullUri = stringify(node) + continue + } + + if (!stmt.uri) { + return result.warn(`Unable to find uri in '${atRule.toString()}'`, { + node: atRule, + }) + } + + if ( + (node.type === "word" || node.type === "function") && + /^layer$/i.test(node.value) + ) { + if (node.nodes) { + layer = [stringify(node.nodes)] } else { - stmt.layer = [""] + layer = [""] } - remainder = remainder.slice(2) + + continue } - } - if (remainder.length > 2) { - if (remainder[1].type !== "space") { - return result.warn("Invalid import media statement", { node: atRule }) + if (node.type === "function" && /^supports$/i.test(node.value)) { + supports = [stringify(node.nodes)] + + continue } - stmt.media = split(remainder, 2) + media = split(params, i) + break + } + + if (!stmt.uri) { + return result.warn(`Unable to find uri in '${atRule.toString()}'`, { + node: atRule, + }) + } + + if (media.length > 0 || layer.length > 0 || supports.length > 0) { + stmt.conditions.push({ + layer, + media, + supports, + }) } return stmt } + +module.exports = parseStatements diff --git a/lib/parse-styles.js b/lib/parse-styles.js index 738d59e0..611912e9 100644 --- a/lib/parse-styles.js +++ b/lib/parse-styles.js @@ -4,28 +4,23 @@ const path = require("path") const assignLayerNames = require("./assign-layer-names") const dataURL = require("./data-url") -const joinLayer = require("./join-layer") -const joinMedia = require("./join-media") const parseStatements = require("./parse-statements") const processContent = require("./process-content") const resolveId = require("./resolve-id") +const checkForCycles = require("./check-for-cycles") async function parseStyles( result, styles, options, state, - media, - layer, + conditions, + from, postcss ) { - const statements = parseStatements(result, styles) + const statements = parseStatements(result, styles, conditions, from) for (const stmt of statements) { - stmt.media = joinMedia(media, stmt.media || []) - stmt.parentMedia = media - stmt.layer = joinLayer(layer, stmt.layer || []) - // skip protocol base uri (protocol://url) or protocol-relative if (stmt.type !== "import" || /^(?:[a-z]+:)?\/\//i.test(stmt.uri)) { continue @@ -70,7 +65,7 @@ async function parseStyles( if (index === 0) child.parent = stmt }) } else imports.push(stmt) - } else if (stmt.type === "media" || stmt.type === "nodes") { + } else if (stmt.type === "nodes") { bundle.push(stmt) } }) @@ -93,6 +88,10 @@ async function resolveImportId(result, stmt, options, state, postcss) { return stmt } + if (dataURL.isValid(stmt.from)) { + return stmt + } + const atRule = stmt.node let sourceFile if (atRule.source?.input?.file) { @@ -143,13 +142,16 @@ async function loadImportContent( postcss ) { const atRule = stmt.node - const { media, layer } = stmt + const stmtDuplicateCheckKey = createStmtDuplicateCheckKey(stmt) + const { conditions, from } = stmt - assignLayerNames(layer, atRule, state, options) + for (const condition of stmt.conditions) { + assignLayerNames(condition.layer, atRule, state, options) + } if (options.skipDuplicates) { // skip files already imported at the same scope - if (state.importedFiles[filename]?.[media]?.[layer]) { + if (state.importedFiles[filename]?.[stmtDuplicateCheckKey]) { return } @@ -157,10 +159,19 @@ async function loadImportContent( if (!state.importedFiles[filename]) { state.importedFiles[filename] = {} } - if (!state.importedFiles[filename][media]) { - state.importedFiles[filename][media] = {} + state.importedFiles[filename][stmtDuplicateCheckKey] = true + } else { + if (!state.importEdges) { + state.importEdges = [] } - state.importedFiles[filename][media][layer] = true + + const copy = [...state.importEdges, [from, filename]] + + if (checkForCycles(copy)) { + return + } + + state.importEdges.push([from, filename]) } const content = await options.load(filename, options) @@ -171,7 +182,10 @@ async function loadImportContent( } // skip previous imported files not containing @import rules - if (state.hashFiles[content]?.[media]?.[layer]) { + if ( + options.skipDuplicates && + state.hashFiles[content]?.[stmtDuplicateCheckKey] + ) { return } @@ -195,15 +209,27 @@ async function loadImportContent( if (!state.hashFiles[content]) { state.hashFiles[content] = {} } - if (!state.hashFiles[content][media]) { - state.hashFiles[content][media] = {} - } - state.hashFiles[content][media][layer] = true + + state.hashFiles[content][stmtDuplicateCheckKey] = true } } // recursion: import @import from imported file - return parseStyles(result, styles, options, state, media, layer, postcss) + return parseStyles( + result, + styles, + options, + state, + conditions, + filename, + postcss + ) +} + +function createStmtDuplicateCheckKey(stmt) { + return JSON.stringify( + stmt.conditions.map(x => [x.media.join(", "), x.layer.join(".")]) + ) } module.exports = parseStyles diff --git a/lib/resolve-id.js b/lib/resolve-id.js index ffef034c..5522b7e6 100644 --- a/lib/resolve-id.js +++ b/lib/resolve-id.js @@ -2,6 +2,7 @@ // external tooling const resolve = require("resolve") +const path = require("path") const moduleDirectories = ["web_modules", "node_modules"] @@ -27,8 +28,8 @@ module.exports = function (id, base, options) { preserveSymlinks: false, } - return resolveModule(`./${id}`, resolveOpts) - .catch(() => resolveModule(id, resolveOpts)) + return resolveModule(`./${path.normalize(id)}`, resolveOpts) + .catch(() => resolveModule(path.normalize(id), resolveOpts)) .catch(() => { if (paths.indexOf(base) === -1) paths.unshift(base) diff --git a/test/fixtures/data-url.expected.css b/test/fixtures/data-url.expected.css index 207253e4..e12aacba 100644 --- a/test/fixtures/data-url.expected.css +++ b/test/fixtures/data-url.expected.css @@ -1,24 +1,6 @@ -p { color: green; } +@import url(foo.css);p { color: green; }p { color: blue; }@media (min-width: 320px){@layer foo{ +p { color: green; } } }@media (min-width: 320px){@layer foo{ -p { color: blue; } - -@media (min-width: 320px) { - - @layer foo { -p { color: green; } } } - -@media (min-width: 320px) { - - @layer foo { - -p { color: blue; } } } - -/* Mixed imports: */ - -foo{} - -p { +p { color: blue; } } }/* Mixed imports: */p { color: blue; -} - -p { color: pink; } +}p { color: pink; } diff --git a/test/fixtures/layer-import-atrules-anonymous.expected.css b/test/fixtures/layer-import-atrules-anonymous.expected.css index 83111e92..2e5d01f1 100644 --- a/test/fixtures/layer-import-atrules-anonymous.expected.css +++ b/test/fixtures/layer-import-atrules-anonymous.expected.css @@ -5,8 +5,11 @@ @import "http://css" layer(named-layer-1.import-anon-layer-6.import-anon-layer-10); @import "http://css-bar" layer(named-layer-1.import-anon-layer-6.bar); @import "http://css-top-level" layer; -@media screen and (min-width: 320px){ -@layer import-anon-layer-0.import-anon-layer-1.import-anon-layer-2{ +@media screen{ +@layer import-anon-layer-0{ +@layer import-anon-layer-1{ +@media (min-width: 320px){ +@layer import-anon-layer-2{ @layer Z { body { color: cyan; @@ -14,8 +17,12 @@ } } } +} +} +} @media screen{ -@layer import-anon-layer-0.import-anon-layer-1{ +@layer import-anon-layer-0{ +@layer import-anon-layer-1{ @layer Y { body { @@ -24,26 +31,19 @@ } } } +} @media screen{ @layer import-anon-layer-0{ body { order: 1; } -} -} -@media screen{ -@layer import-anon-layer-0{ @media (min-width: 50rem) { body { order: 2; } } -} -} -@media screen{ -@layer import-anon-layer-0{ @keyframes RED_TO_BLUE { 0% { @@ -68,8 +68,10 @@ body { } } } +@layer import-anon-layer-3{ +@layer import-anon-layer-4{ @media (min-width: 320px){ -@layer import-anon-layer-3.import-anon-layer-4.import-anon-layer-5{ +@layer import-anon-layer-5{ @layer Z { body { color: cyan; @@ -77,7 +79,10 @@ body { } } } -@layer import-anon-layer-3.import-anon-layer-4{ +} +} +@layer import-anon-layer-3{ +@layer import-anon-layer-4{ @layer Y { body { @@ -85,21 +90,18 @@ body { } } } +} @layer import-anon-layer-3{ body { order: 1; } -} -@layer import-anon-layer-3{ @media (min-width: 50rem) { body { order: 2; } } -} -@layer import-anon-layer-3{ @keyframes RED_TO_BLUE { 0% { @@ -123,8 +125,10 @@ body { } } } +@layer named-layer-1{ +@layer import-anon-layer-6{ @media (min-width: 320px){ -@layer named-layer-1.import-anon-layer-6.import-anon-layer-7{ +@layer import-anon-layer-7{ @layer Z { body { color: cyan; @@ -132,7 +136,10 @@ body { } } } -@layer named-layer-1.import-anon-layer-6{ +} +} +@layer named-layer-1{ +@layer import-anon-layer-6{ @layer Y { body { @@ -140,21 +147,18 @@ body { } } } +} @layer named-layer-1{ body { order: 1; } -} -@layer named-layer-1{ @media (min-width: 50rem) { body { order: 2; } } -} -@layer named-layer-1{ @keyframes RED_TO_BLUE { 0% { diff --git a/test/fixtures/layer-import-atrules.expected.css b/test/fixtures/layer-import-atrules.expected.css index e4d6c195..636333bd 100644 --- a/test/fixtures/layer-import-atrules.expected.css +++ b/test/fixtures/layer-import-atrules.expected.css @@ -1,5 +1,8 @@ -@media screen and (min-width: 320px) { -@layer imported-with-media.level-2.level-3 { +@media screen { +@layer imported-with-media { +@layer level-2 { +@media (min-width: 320px) { +@layer level-3 { @layer Z { body { color: cyan; @@ -7,9 +10,13 @@ } } } +} +} +} @media screen { -@layer imported-with-media.level-2 { +@layer imported-with-media { +@layer level-2 { @layer Y { body { @@ -18,6 +25,7 @@ } } } +} @media screen { @layer imported-with-media { @@ -25,22 +33,12 @@ body { order: 1; } -} -} - -@media screen { -@layer imported-with-media { @media (min-width: 50rem) { body { order: 2; } } -} -} - -@media screen { -@layer imported-with-media { @keyframes RED_TO_BLUE { 0% { @@ -66,8 +64,10 @@ body { } } +@layer imported-without-media { +@layer level-2 { @media (min-width: 320px) { -@layer imported-without-media.level-2.level-3 { +@layer level-3 { @layer Z { body { color: cyan; @@ -75,8 +75,11 @@ body { } } } +} +} -@layer imported-without-media.level-2 { +@layer imported-without-media { +@layer level-2 { @layer Y { body { @@ -84,24 +87,19 @@ body { } } } +} @layer imported-without-media { body { order: 1; } -} - -@layer imported-without-media { @media (min-width: 50rem) { body { order: 2; } } -} - -@layer imported-without-media { @keyframes RED_TO_BLUE { 0% { diff --git a/test/fixtures/layer-rule-grouping.expected.css b/test/fixtures/layer-rule-grouping.expected.css index a4e34d0e..e292d4f7 100644 --- a/test/fixtures/layer-rule-grouping.expected.css +++ b/test/fixtures/layer-rule-grouping.expected.css @@ -3,61 +3,29 @@ rule-one {} rule-two {} -} - -@media screen and (min-width: 50rem) { - rule-three {} - - rule-four {} -} - -@media screen { - -rule-five {} - -rule-six {} -} - -@layer import-anon-layer-0 { - -rule-one {} - -rule-two {} -} - -@layer import-anon-layer-0 { @media (min-width: 50rem) { rule-three {} rule-four {} } -} - -@layer import-anon-layer-0 { rule-five {} rule-six {} } -@layer import-anon-layer-1 { +@layer import-anon-layer-0 { rule-one {} rule-two {} -} - -@layer import-anon-layer-1 { @media (min-width: 50rem) { rule-three {} rule-four {} } -} - -@layer import-anon-layer-1 { rule-five {} @@ -69,18 +37,12 @@ rule-six {} rule-one {} rule-two {} -} - -@layer named { @media (min-width: 50rem) { rule-three {} rule-four {} } -} - -@layer named { rule-five {} diff --git a/test/fixtures/media-join.expected.css b/test/fixtures/media-join.expected.css index 0dd4562e..6f4a8984 100644 --- a/test/fixtures/media-join.expected.css +++ b/test/fixtures/media-join.expected.css @@ -1,17 +1,26 @@ -@media one-1 and one-2 {} +@media one-1 { -@media one-1 and or-left-2, one-1 and or-right-2 {} +@media one-2 {} -@media one-1 and and-left-2 and and-right-2 {} +@media or-left-2, or-right-2 {} -@media or-left-1 and one-2, or-right-1 and one-2 {} +@media and-left-2 and and-right-2 {} +} -@media or-left-1 and or-left-2, or-left-1 and or-right-2, or-right-1 and or-left-2, or-right-1 and or-right-2 {} +@media or-left-1, or-right-1 { -@media or-left-1 and and-left-2 and and-right-2, or-right-1 and and-left-2 and and-right-2 {} +@media one-2 {} -@media and-left-1 and and-right-1 and one-2 {} +@media or-left-2, or-right-2 {} -@media and-left-1 and and-right-1 and or-left-2, and-left-1 and and-right-1 and or-right-2 {} +@media and-left-2 and and-right-2 {} +} -@media and-left-1 and and-right-1 and and-left-2 and and-right-2 {} +@media and-left-1 and and-right-1 { + +@media one-2 {} + +@media or-left-2, or-right-2 {} + +@media and-left-2 and and-right-2 {} +} diff --git a/test/fixtures/media-query.expected.css b/test/fixtures/media-query.expected.css index 20ec66fc..5c0a1449 100644 --- a/test/fixtures/media-query.expected.css +++ b/test/fixtures/media-query.expected.css @@ -1,5 +1,12 @@ -@media level-1 and level-2 and level-3 {} +@media level-1 { +@media level-2 { +@media level-3 {} +} +} -@media level-1 and level-2 {} +@media level-1 { + +@media level-2 {} +} @media level-1 {}