diff --git a/lib/apply-conditions.js b/lib/apply-conditions.js index 76a8475..2005c6b 100644 --- a/lib/apply-conditions.js +++ b/lib/apply-conditions.js @@ -3,12 +3,38 @@ const base64EncodedConditionalImport = require("./base64-encoded-import") module.exports = function applyConditions(bundle, atRule) { - bundle.forEach(stmt => { + const firstImportStatementIndex = bundle.findIndex( + stmt => stmt.type === "import", + ) + const lastImportStatementIndex = bundle.findLastIndex( + stmt => stmt.type === "import", + ) + + bundle.forEach((stmt, index) => { + if (stmt.type === "charset" || stmt.type === "warning") { + return + } + if ( - stmt.type === "charset" || - stmt.type === "warning" || - !stmt.conditions?.length + stmt.type === "layer" && + ((index < lastImportStatementIndex && stmt.conditions?.length) || + (index > firstImportStatementIndex && index < lastImportStatementIndex)) ) { + stmt.type = "import" + stmt.node = stmt.node.clone({ + name: "import", + params: base64EncodedConditionalImport( + `'data:text/css;base64,${Buffer.from(stmt.node.toString()).toString( + "base64", + )}'`, + stmt.conditions, + ), + }) + + return + } + + if (!stmt.conditions?.length) { return } @@ -20,8 +46,15 @@ module.exports = function applyConditions(bundle, atRule) { return } - const { nodes } = stmt - const { parent } = nodes[0] + let nodes + let parent + if (stmt.type === "layer") { + nodes = [stmt.node] + parent = stmt.node.parent + } else { + nodes = stmt.nodes + parent = nodes[0].parent + } const atRules = [] diff --git a/lib/apply-styles.js b/lib/apply-styles.js index ca961b1..a92e7ff 100644 --- a/lib/apply-styles.js +++ b/lib/apply-styles.js @@ -5,7 +5,7 @@ module.exports = function applyStyles(bundle, styles) { // Strip additional statements. bundle.forEach(stmt => { - if (["charset", "import"].includes(stmt.type)) { + if (["charset", "import", "layer"].includes(stmt.type)) { stmt.node.parent = undefined styles.append(stmt.node) } else if (stmt.type === "nodes") { diff --git a/lib/base64-encoded-import.js b/lib/base64-encoded-import.js index a928c62..5e49779 100644 --- a/lib/base64-encoded-import.js +++ b/lib/base64-encoded-import.js @@ -8,6 +8,8 @@ const formatImportPrelude = require("./format-import-prelude") // To achieve this we create a list of base64 encoded imports, where each import contains a stylesheet with another import. // Each import can define a single group of conditions and a single cascade layer. module.exports = function base64EncodedConditionalImport(prelude, conditions) { + if (!conditions?.length) return prelude + conditions.reverse() const first = conditions.pop() let params = `${prelude} ${formatImportPrelude( diff --git a/lib/parse-statements.js b/lib/parse-statements.js index 788193a..3df0bad 100644 --- a/lib/parse-statements.js +++ b/lib/parse-statements.js @@ -9,6 +9,7 @@ const { stringify } = valueParser module.exports = function parseStatements(result, styles, conditions, from) { const statements = [] let nodes = [] + let encounteredNonImportNodes = false styles.each(node => { let stmt @@ -17,6 +18,14 @@ module.exports = function parseStatements(result, styles, conditions, from) { stmt = parseImport(result, node, conditions, from) else if (node.name === "charset") stmt = parseCharset(result, node, conditions, from) + else if ( + node.name === "layer" && + !encounteredNonImportNodes && + !node.nodes + ) + stmt = parseLayer(result, node, conditions, from) + } else if (node.type !== "comment") { + encounteredNonImportNodes = true } if (stmt) { @@ -233,3 +242,12 @@ function parseImport(result, atRule, conditions, from) { return stmt } + +function parseLayer(result, atRule, conditions, from) { + return { + type: "layer", + node: atRule, + conditions: [...conditions], + from, + } +} diff --git a/lib/parse-styles.js b/lib/parse-styles.js index 9dd43d7..766de9c 100644 --- a/lib/parse-styles.js +++ b/lib/parse-styles.js @@ -33,7 +33,7 @@ async function parseStyles( } let charset - const imports = [] + const beforeBundle = [] const bundle = [] function handleCharset(stmt) { @@ -56,19 +56,24 @@ async function parseStyles( else if (stmt.type === "import") { if (stmt.children) { stmt.children.forEach((child, index) => { - if (child.type === "import") imports.push(child) + if (child.type === "import") beforeBundle.push(child) + else if (child.type === "layer") beforeBundle.push(child) else if (child.type === "charset") handleCharset(child) else bundle.push(child) // For better output if (index === 0) child.parent = stmt }) - } else imports.push(stmt) + } else beforeBundle.push(stmt) + } else if (stmt.type === "layer") { + beforeBundle.push(stmt) } else if (stmt.type === "nodes") { bundle.push(stmt) } }) - return charset ? [charset, ...imports.concat(bundle)] : imports.concat(bundle) + return charset + ? [charset, ...beforeBundle.concat(bundle)] + : beforeBundle.concat(bundle) } async function resolveImportId(result, stmt, options, state, postcss) { diff --git a/test/fixtures/imports/layer-followed-by-ignore.css b/test/fixtures/imports/layer-followed-by-ignore.css new file mode 100644 index 0000000..13f9646 --- /dev/null +++ b/test/fixtures/imports/layer-followed-by-ignore.css @@ -0,0 +1,4 @@ +/* a comment */ + +@layer layer-alpha; +@import "http://css"; diff --git a/test/fixtures/imports/layer-only.css b/test/fixtures/imports/layer-only.css new file mode 100644 index 0000000..9dc1acc --- /dev/null +++ b/test/fixtures/imports/layer-only.css @@ -0,0 +1 @@ +@layer layer-beta; diff --git a/test/fixtures/layer-followed-by-ignore-with-conditions.css b/test/fixtures/layer-followed-by-ignore-with-conditions.css new file mode 100644 index 0000000..e5cf2e5 --- /dev/null +++ b/test/fixtures/layer-followed-by-ignore-with-conditions.css @@ -0,0 +1,2 @@ +@import "layer-only.css" print; +@import "layer-followed-by-ignore.css" screen; diff --git a/test/fixtures/layer-followed-by-ignore-with-conditions.expected.css b/test/fixtures/layer-followed-by-ignore-with-conditions.expected.css new file mode 100644 index 0000000..3d1a219 --- /dev/null +++ b/test/fixtures/layer-followed-by-ignore-with-conditions.expected.css @@ -0,0 +1,6 @@ +@import 'data:text/css;base64,QGxheWVyIGxheWVyLWJldGE=' print; +@import 'data:text/css;base64,QGxheWVyIGxheWVyLWFscGhh' screen; +@import "http://css" screen; +@media screen{ +/* a comment */ +} diff --git a/test/fixtures/layer-followed-by-ignore-without-conditions.css b/test/fixtures/layer-followed-by-ignore-without-conditions.css new file mode 100644 index 0000000..6b48f60 --- /dev/null +++ b/test/fixtures/layer-followed-by-ignore-without-conditions.css @@ -0,0 +1,3 @@ +@import "http://css-a"; +@import url("layer-only.css"); +@import "http://css-b"; diff --git a/test/fixtures/layer-followed-by-ignore-without-conditions.expected.css b/test/fixtures/layer-followed-by-ignore-without-conditions.expected.css new file mode 100644 index 0000000..6e8818a --- /dev/null +++ b/test/fixtures/layer-followed-by-ignore-without-conditions.expected.css @@ -0,0 +1,3 @@ +@import "http://css-a"; +@import 'data:text/css;base64,QGxheWVyIGxheWVyLWJldGE='; +@import "http://css-b"; diff --git a/test/fixtures/layer-followed-by-ignore.css b/test/fixtures/layer-followed-by-ignore.css new file mode 100644 index 0000000..1c4c793 --- /dev/null +++ b/test/fixtures/layer-followed-by-ignore.css @@ -0,0 +1,2 @@ +@layer layer-alpha; +@import "http://css"; diff --git a/test/fixtures/layer-followed-by-ignore.expected.css b/test/fixtures/layer-followed-by-ignore.expected.css new file mode 100644 index 0000000..1c4c793 --- /dev/null +++ b/test/fixtures/layer-followed-by-ignore.expected.css @@ -0,0 +1,2 @@ +@layer layer-alpha; +@import "http://css"; diff --git a/test/fixtures/layer-statement-with-conditions.css b/test/fixtures/layer-statement-with-conditions.css new file mode 100644 index 0000000..2256df4 --- /dev/null +++ b/test/fixtures/layer-statement-with-conditions.css @@ -0,0 +1 @@ +@import "layer-only.css" print; diff --git a/test/fixtures/layer-statement-with-conditions.expected.css b/test/fixtures/layer-statement-with-conditions.expected.css new file mode 100644 index 0000000..9f57df6 --- /dev/null +++ b/test/fixtures/layer-statement-with-conditions.expected.css @@ -0,0 +1,3 @@ +@media print{ +@layer layer-beta +} diff --git a/test/layer.js b/test/layer.js index c80cc32..52236a6 100644 --- a/test/layer.js +++ b/test/layer.js @@ -33,3 +33,27 @@ test( checkFixture, "layer-duplicate-anonymous-imports-skip", ) + +test( + "should correctly handle layer statements followed by ignored imports", + checkFixture, + "layer-followed-by-ignore", +) + +test( + "should correctly handle layer statements followed by ignored imports in conditional imports", + checkFixture, + "layer-followed-by-ignore-with-conditions", +) + +test( + "should correctly handle layer statements followed by ignored imports in unconditional imports", + checkFixture, + "layer-followed-by-ignore-without-conditions", +) + +test( + "should correctly handle layer statements in conditional imports", + checkFixture, + "layer-statement-with-conditions", +)