From 84a6011a9ffe4d8ba601b377ed69a37e22340a04 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Mon, 27 Jun 2022 19:32:16 +0200 Subject: [PATCH 1/9] fix import with layer --- index.js | 38 ++++++++++- test/fixtures/imports/layer-level-2.css | 7 ++ test/fixtures/imports/layer-level-3.css | 5 ++ test/fixtures/imports/layer.css | 33 +++++++++ test/fixtures/layer-import-atrules.css | 1 + .../layer-import-atrules.expected.css | 67 +++++++++++++++++++ test/layer.js | 6 ++ 7 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/imports/layer-level-2.css create mode 100644 test/fixtures/imports/layer-level-3.css create mode 100644 test/fixtures/imports/layer.css create mode 100644 test/fixtures/layer-import-atrules.css create mode 100644 test/fixtures/layer-import-atrules.expected.css diff --git a/index.js b/index.js index e6a3dc33..d32e58dc 100755 --- a/index.js +++ b/index.js @@ -79,9 +79,41 @@ function AtImport(options) { } if (stmt.type === "import") { - stmt.node.params = `${stmt.fullUri} ${stmt.media.join(", ")}` + const media = stmt.media.join(", ") + if (stmt.layer.length) { + const layerName = stmt.layer + .filter(layer => layer !== "") + .join(".") + + let layerParams = "layer" + if (layerName) { + layerParams = `layer(${layerName})` + } + + stmt.node.params = `${stmt.fullUri} ${layerParams})${media}` + } else { + stmt.node.params = `${stmt.fullUri} ${media}` + } } else if (stmt.type === "media") { - stmt.node.params = stmt.media.join(", ") + if (stmt.layer.length) { + const layerNode = atRule({ + name: "layer", + params: stmt.layer.filter(layer => layer !== "").join("."), + source: stmt.node.source, + }) + + const mediaNode = atRule({ + name: "media", + params: stmt.parentMedia.join(", "), + source: stmt.node.source, + }) + + mediaNode.append(layerNode) + layerNode.append(stmt.node) + stmt.node = mediaNode + } else { + stmt.node.params = stmt.media.join(", ") + } } else { const { nodes } = stmt const { parent } = nodes[0] @@ -170,7 +202,9 @@ function AtImport(options) { return stmts.reduce((promise, stmt) => { return promise.then(() => { stmt.media = joinMedia(media, stmt.media || []) + stmt.parentMedia = media stmt.layer = joinLayer(layer, stmt.layer || []) + stmt.parentLayer = layer // skip protocol base uri (protocol://url) or protocol-relative if ( diff --git a/test/fixtures/imports/layer-level-2.css b/test/fixtures/imports/layer-level-2.css new file mode 100644 index 00000000..8ec5e25b --- /dev/null +++ b/test/fixtures/imports/layer-level-2.css @@ -0,0 +1,7 @@ +@import url("layer-level-3.css") layer(level-3) (min-width: 320px); + +@layer Y { + body { + color: purple; + } +} diff --git a/test/fixtures/imports/layer-level-3.css b/test/fixtures/imports/layer-level-3.css new file mode 100644 index 00000000..1567ae96 --- /dev/null +++ b/test/fixtures/imports/layer-level-3.css @@ -0,0 +1,5 @@ +@layer Z { + body { + color: cyan; + } +} diff --git a/test/fixtures/imports/layer.css b/test/fixtures/imports/layer.css new file mode 100644 index 00000000..9e554b76 --- /dev/null +++ b/test/fixtures/imports/layer.css @@ -0,0 +1,33 @@ +@import url("layer-level-2.css") layer(level-2); + +body { + order: 1; +} + +@media (min-width: 50rem) { + body { + order: 2; + } +} + +@keyframes RED_TO_BLUE { + 0% { + background-color: red; + } + 100% { + background-color: blue; + } +} + +@supports (display: grid) { + body { + display: grid; + order: 3; + } +} + +@layer A { + body { + order: 4; + } +} diff --git a/test/fixtures/layer-import-atrules.css b/test/fixtures/layer-import-atrules.css new file mode 100644 index 00000000..6fda26d8 --- /dev/null +++ b/test/fixtures/layer-import-atrules.css @@ -0,0 +1 @@ +@import url("layer.css") layer(imported) screen; diff --git a/test/fixtures/layer-import-atrules.expected.css b/test/fixtures/layer-import-atrules.expected.css new file mode 100644 index 00000000..acc7f504 --- /dev/null +++ b/test/fixtures/layer-import-atrules.expected.css @@ -0,0 +1,67 @@ +@media screen and (min-width: 320px) { + @layer imported.level-2.level-3 { +@layer Z { + body { + color: cyan; + } +} + } +} + +@media screen { + @layer imported.level-2 { + +@layer Y { + body { + color: purple; + } +} + } +} + +@media screen { + @layer imported { + +body { + order: 1; +} + } +} + +@media screen { + @layer imported { + +@media (min-width: 50rem) { + body { + order: 2; + } +} + } +} + +@media screen { + @layer imported { + +@keyframes RED_TO_BLUE { + 0% { + background-color: red; + } + 100% { + background-color: blue; + } +} + +@supports (display: grid) { + body { + display: grid; + order: 3; + } +} + +@layer A { + body { + order: 4; + } +} + } +} diff --git a/test/layer.js b/test/layer.js index 1b50f4c5..0620fa44 100644 --- a/test/layer.js +++ b/test/layer.js @@ -6,3 +6,9 @@ const test = require("ava") const checkFixture = require("./helpers/check-fixture") test("should resolve layers of import statements", checkFixture, "layer") + +test( + "should correctly wrap imported at rules in layers", + checkFixture, + "layer-import-atrules" +) From 43af03ff82f8130e4571e87c2d8bc531522414ac Mon Sep 17 00:00:00 2001 From: romainmenke Date: Mon, 27 Jun 2022 22:59:44 +0200 Subject: [PATCH 2/9] one more fix --- index.js | 21 +++-- test/fixtures/layer-import-atrules.css | 4 +- .../layer-import-atrules.expected.css | 80 ++++++++++++++++--- 3 files changed, 86 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index d32e58dc..7d2866e8 100755 --- a/index.js +++ b/index.js @@ -102,15 +102,20 @@ function AtImport(options) { source: stmt.node.source, }) - const mediaNode = atRule({ - name: "media", - params: stmt.parentMedia.join(", "), - source: stmt.node.source, - }) + if (stmt.parentMedia && 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 + mediaNode.append(layerNode) + layerNode.append(stmt.node) + stmt.node = mediaNode + } else { + layerNode.append(stmt.node) + stmt.node = layerNode + } } else { stmt.node.params = stmt.media.join(", ") } diff --git a/test/fixtures/layer-import-atrules.css b/test/fixtures/layer-import-atrules.css index 6fda26d8..dab4c0e7 100644 --- a/test/fixtures/layer-import-atrules.css +++ b/test/fixtures/layer-import-atrules.css @@ -1 +1,3 @@ -@import url("layer.css") layer(imported) screen; +@import url("layer.css") layer(imported-with-media) screen; + +@import url("layer.css") layer(imported-without-media); diff --git a/test/fixtures/layer-import-atrules.expected.css b/test/fixtures/layer-import-atrules.expected.css index acc7f504..e4d6c195 100644 --- a/test/fixtures/layer-import-atrules.expected.css +++ b/test/fixtures/layer-import-atrules.expected.css @@ -1,46 +1,107 @@ @media screen and (min-width: 320px) { - @layer imported.level-2.level-3 { +@layer imported-with-media.level-2.level-3 { @layer Z { body { color: cyan; } } - } +} } @media screen { - @layer imported.level-2 { +@layer imported-with-media.level-2 { @layer Y { body { color: purple; } } - } +} } @media screen { - @layer imported { +@layer imported-with-media { body { order: 1; } - } +} } @media screen { - @layer imported { +@layer imported-with-media { @media (min-width: 50rem) { body { order: 2; } } - } +} } @media screen { - @layer imported { +@layer imported-with-media { + +@keyframes RED_TO_BLUE { + 0% { + background-color: red; + } + 100% { + background-color: blue; + } +} + +@supports (display: grid) { + body { + display: grid; + order: 3; + } +} + +@layer A { + body { + order: 4; + } +} +} +} + +@media (min-width: 320px) { +@layer imported-without-media.level-2.level-3 { +@layer Z { + body { + color: cyan; + } +} +} +} + +@layer imported-without-media.level-2 { + +@layer Y { + body { + color: purple; + } +} +} + +@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% { @@ -63,5 +124,4 @@ body { order: 4; } } - } } From 5dc3623891d95bfb4d79d609b8f84c3084a8c150 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Fri, 5 Aug 2022 10:46:30 +0200 Subject: [PATCH 3/9] remove unreachable code --- index.js | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/index.js b/index.js index 7d2866e8..3b7626ba 100755 --- a/index.js +++ b/index.js @@ -79,21 +79,7 @@ function AtImport(options) { } if (stmt.type === "import") { - const media = stmt.media.join(", ") - if (stmt.layer.length) { - const layerName = stmt.layer - .filter(layer => layer !== "") - .join(".") - - let layerParams = "layer" - if (layerName) { - layerParams = `layer(${layerName})` - } - - stmt.node.params = `${stmt.fullUri} ${layerParams})${media}` - } else { - stmt.node.params = `${stmt.fullUri} ${media}` - } + stmt.node.params = `${stmt.fullUri} ${stmt.media.join(", ")}` } else if (stmt.type === "media") { if (stmt.layer.length) { const layerNode = atRule({ @@ -209,7 +195,6 @@ function AtImport(options) { stmt.media = joinMedia(media, stmt.media || []) stmt.parentMedia = media stmt.layer = joinLayer(layer, stmt.layer || []) - stmt.parentLayer = layer // skip protocol base uri (protocol://url) or protocol-relative if ( From 43fea6cfd9913335d1e8460206ed0dfabc4068bc Mon Sep 17 00:00:00 2001 From: romainmenke Date: Fri, 5 Aug 2022 11:54:28 +0200 Subject: [PATCH 4/9] fix anonymous layers --- index.js | 33 +++++-- test/fixtures/imports/layer-rule-grouping.css | 13 +++ test/fixtures/layer-rule-grouping.css | 6 ++ .../fixtures/layer-rule-grouping.expected.css | 88 +++++++++++++++++++ test/fixtures/layer.expected.css | 4 +- test/layer.js | 2 + 6 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 test/fixtures/imports/layer-rule-grouping.css create mode 100644 test/fixtures/layer-rule-grouping.css create mode 100644 test/fixtures/layer-rule-grouping.expected.css diff --git a/index.js b/index.js index 3b7626ba..b5be6f1d 100755 --- a/index.js +++ b/index.js @@ -37,6 +37,7 @@ function AtImport(options) { const state = { importedFiles: {}, hashFiles: {}, + anonymousLayerCounter: 0, } if (styles.source && styles.source.input && styles.source.input.file) { @@ -307,18 +308,28 @@ function AtImport(options) { function loadImportContent(result, stmt, filename, options, state) { const atRule = stmt.node const { media, layer } = stmt + if (layer.length === 1 && layer[0] === "") { + layer[0] = `importted-anon-layer-${state.anonymousLayerCounter++}` + } + if (options.skipDuplicates) { // skip files already imported at the same scope if ( state.importedFiles[filename] && - state.importedFiles[filename][media] + state.importedFiles[filename][media] && + state.importedFiles[filename][media][layer] ) { return } // save imported files to skip them next time - if (!state.importedFiles[filename]) state.importedFiles[filename] = {} - state.importedFiles[filename][media] = true + if (!state.importedFiles[filename]) { + state.importedFiles[filename] = {} + } + if (!state.importedFiles[filename][media]) { + state.importedFiles[filename][media] = {} + } + state.importedFiles[filename][media][layer] = true } return Promise.resolve(options.load(filename, options)).then( @@ -329,8 +340,13 @@ function AtImport(options) { } // skip previous imported files not containing @import rules - if (state.hashFiles[content] && state.hashFiles[content][media]) + if ( + state.hashFiles[content] && + state.hashFiles[content][media] && + state.hashFiles[content][media][layer] + ) { return + } return processContent( result, @@ -348,8 +364,13 @@ function AtImport(options) { }) if (!hasImport) { // save hash files to skip them next time - if (!state.hashFiles[content]) state.hashFiles[content] = {} - state.hashFiles[content][media] = true + if (!state.hashFiles[content]) { + state.hashFiles[content] = {} + } + if (!state.hashFiles[content][media]) { + state.hashFiles[content][media] = {} + } + state.hashFiles[content][media][layer] = true } } diff --git a/test/fixtures/imports/layer-rule-grouping.css b/test/fixtures/imports/layer-rule-grouping.css new file mode 100644 index 00000000..c3febe5e --- /dev/null +++ b/test/fixtures/imports/layer-rule-grouping.css @@ -0,0 +1,13 @@ +rule-one {} + +rule-two {} + +@media (min-width: 50rem) { + rule-three {} + + rule-four {} +} + +rule-five {} + +rule-six {} diff --git a/test/fixtures/layer-rule-grouping.css b/test/fixtures/layer-rule-grouping.css new file mode 100644 index 00000000..afa489e5 --- /dev/null +++ b/test/fixtures/layer-rule-grouping.css @@ -0,0 +1,6 @@ +@import url("layer-rule-grouping.css") screen; + +@import url("layer-rule-grouping.css") layer; +@import url("layer-rule-grouping.css") layer; + +@import url("layer-rule-grouping.css") layer(named); diff --git a/test/fixtures/layer-rule-grouping.expected.css b/test/fixtures/layer-rule-grouping.expected.css new file mode 100644 index 00000000..1cd1d636 --- /dev/null +++ b/test/fixtures/layer-rule-grouping.expected.css @@ -0,0 +1,88 @@ +@media screen { + +rule-one {} + +rule-two {} +} + +@media screen and (min-width: 50rem) { + rule-three {} + + rule-four {} +} + +@media screen { + +rule-five {} + +rule-six {} +} + +@layer importted-anon-layer-0 { + +rule-one {} + +rule-two {} +} + +@layer importted-anon-layer-0 { + +@media (min-width: 50rem) { + rule-three {} + + rule-four {} +} +} + +@layer importted-anon-layer-0 { + +rule-five {} + +rule-six {} +} + +@layer importted-anon-layer-1 { + +rule-one {} + +rule-two {} +} + +@layer importted-anon-layer-1 { + +@media (min-width: 50rem) { + rule-three {} + + rule-four {} +} +} + +@layer importted-anon-layer-1 { + +rule-five {} + +rule-six {} +} + +@layer named { + +rule-one {} + +rule-two {} +} + +@layer named { + +@media (min-width: 50rem) { + rule-three {} + + rule-four {} +} +} + +@layer named { + +rule-five {} + +rule-six {} +} diff --git a/test/fixtures/layer.expected.css b/test/fixtures/layer.expected.css index 0a189869..95729c2a 100644 --- a/test/fixtures/layer.expected.css +++ b/test/fixtures/layer.expected.css @@ -1,5 +1,5 @@ @layer layer-alpha, layer-beta.one; -@layer{ +@layer importted-anon-layer-0{ foo{} } @layer bar{ @@ -10,7 +10,7 @@ bar{} bar{} } } -@layer{ +@layer importted-anon-layer-1{ baz{} } @layer foobar{ diff --git a/test/layer.js b/test/layer.js index 0620fa44..10ff1f20 100644 --- a/test/layer.js +++ b/test/layer.js @@ -12,3 +12,5 @@ test( checkFixture, "layer-import-atrules" ) + +test("should group rules", checkFixture, "layer-rule-grouping") From 2422172fc63fd4e14e6cd7dc504af221d7fe9bc5 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Fri, 5 Aug 2022 12:05:59 +0200 Subject: [PATCH 5/9] fix anonymous layers --- index.js | 8 +- test/fixtures/imports/layer-anonymous.css | 33 +++++ .../imports/layer-level-2-anonymous.css | 7 + .../layer-import-atrules-anonymous.css | 3 + ...ayer-import-atrules-anonymous.expected.css | 127 ++++++++++++++++++ .../fixtures/layer-rule-grouping.expected.css | 12 +- test/fixtures/layer.expected.css | 4 +- test/layer.js | 6 + 8 files changed, 189 insertions(+), 11 deletions(-) create mode 100644 test/fixtures/imports/layer-anonymous.css create mode 100644 test/fixtures/imports/layer-level-2-anonymous.css create mode 100644 test/fixtures/layer-import-atrules-anonymous.css create mode 100644 test/fixtures/layer-import-atrules-anonymous.expected.css diff --git a/index.js b/index.js index b5be6f1d..083579a7 100755 --- a/index.js +++ b/index.js @@ -308,9 +308,11 @@ function AtImport(options) { function loadImportContent(result, stmt, filename, options, state) { const atRule = stmt.node const { media, layer } = stmt - if (layer.length === 1 && layer[0] === "") { - layer[0] = `importted-anon-layer-${state.anonymousLayerCounter++}` - } + layer.forEach((layerPart, i) => { + if (layerPart === "") { + layer[i] = `imported-anon-layer-${state.anonymousLayerCounter++}` + } + }) if (options.skipDuplicates) { // skip files already imported at the same scope diff --git a/test/fixtures/imports/layer-anonymous.css b/test/fixtures/imports/layer-anonymous.css new file mode 100644 index 00000000..589aeeb1 --- /dev/null +++ b/test/fixtures/imports/layer-anonymous.css @@ -0,0 +1,33 @@ +@import url("layer-level-2-anonymous.css") layer; + +body { + order: 1; +} + +@media (min-width: 50rem) { + body { + order: 2; + } +} + +@keyframes RED_TO_BLUE { + 0% { + background-color: red; + } + 100% { + background-color: blue; + } +} + +@supports (display: grid) { + body { + display: grid; + order: 3; + } +} + +@layer A { + body { + order: 4; + } +} diff --git a/test/fixtures/imports/layer-level-2-anonymous.css b/test/fixtures/imports/layer-level-2-anonymous.css new file mode 100644 index 00000000..36d69c28 --- /dev/null +++ b/test/fixtures/imports/layer-level-2-anonymous.css @@ -0,0 +1,7 @@ +@import url("layer-level-3.css") layer (min-width: 320px); + +@layer Y { + body { + color: purple; + } +} diff --git a/test/fixtures/layer-import-atrules-anonymous.css b/test/fixtures/layer-import-atrules-anonymous.css new file mode 100644 index 00000000..f2d93560 --- /dev/null +++ b/test/fixtures/layer-import-atrules-anonymous.css @@ -0,0 +1,3 @@ +@import url("layer-anonymous.css") layer screen; + +@import url("layer-anonymous.css") layer; diff --git a/test/fixtures/layer-import-atrules-anonymous.expected.css b/test/fixtures/layer-import-atrules-anonymous.expected.css new file mode 100644 index 00000000..594b7afd --- /dev/null +++ b/test/fixtures/layer-import-atrules-anonymous.expected.css @@ -0,0 +1,127 @@ +@media screen and (min-width: 320px) { +@layer imported-anon-layer-0.imported-anon-layer-1.imported-anon-layer-2 { +@layer Z { + body { + color: cyan; + } +} +} +} + +@media screen { +@layer imported-anon-layer-0.imported-anon-layer-1 { + +@layer Y { + body { + color: purple; + } +} +} +} + +@media screen { +@layer imported-anon-layer-0 { + +body { + order: 1; +} +} +} + +@media screen { +@layer imported-anon-layer-0 { + +@media (min-width: 50rem) { + body { + order: 2; + } +} +} +} + +@media screen { +@layer imported-anon-layer-0 { + +@keyframes RED_TO_BLUE { + 0% { + background-color: red; + } + 100% { + background-color: blue; + } +} + +@supports (display: grid) { + body { + display: grid; + order: 3; + } +} + +@layer A { + body { + order: 4; + } +} +} +} + +@media (min-width: 320px) { +@layer imported-anon-layer-3.imported-anon-layer-4.imported-anon-layer-5 { +@layer Z { + body { + color: cyan; + } +} +} +} + +@layer imported-anon-layer-3.imported-anon-layer-4 { + +@layer Y { + body { + color: purple; + } +} +} + +@layer imported-anon-layer-3 { + +body { + order: 1; +} +} + +@layer imported-anon-layer-3 { + +@media (min-width: 50rem) { + body { + order: 2; + } +} +} + +@layer imported-anon-layer-3 { + +@keyframes RED_TO_BLUE { + 0% { + background-color: red; + } + 100% { + background-color: blue; + } +} + +@supports (display: grid) { + body { + display: grid; + order: 3; + } +} + +@layer A { + body { + order: 4; + } +} +} diff --git a/test/fixtures/layer-rule-grouping.expected.css b/test/fixtures/layer-rule-grouping.expected.css index 1cd1d636..05c0cbf5 100644 --- a/test/fixtures/layer-rule-grouping.expected.css +++ b/test/fixtures/layer-rule-grouping.expected.css @@ -18,14 +18,14 @@ rule-five {} rule-six {} } -@layer importted-anon-layer-0 { +@layer imported-anon-layer-0 { rule-one {} rule-two {} } -@layer importted-anon-layer-0 { +@layer imported-anon-layer-0 { @media (min-width: 50rem) { rule-three {} @@ -34,21 +34,21 @@ rule-two {} } } -@layer importted-anon-layer-0 { +@layer imported-anon-layer-0 { rule-five {} rule-six {} } -@layer importted-anon-layer-1 { +@layer imported-anon-layer-1 { rule-one {} rule-two {} } -@layer importted-anon-layer-1 { +@layer imported-anon-layer-1 { @media (min-width: 50rem) { rule-three {} @@ -57,7 +57,7 @@ rule-two {} } } -@layer importted-anon-layer-1 { +@layer imported-anon-layer-1 { rule-five {} diff --git a/test/fixtures/layer.expected.css b/test/fixtures/layer.expected.css index 95729c2a..65fa65d0 100644 --- a/test/fixtures/layer.expected.css +++ b/test/fixtures/layer.expected.css @@ -1,5 +1,5 @@ @layer layer-alpha, layer-beta.one; -@layer importted-anon-layer-0{ +@layer imported-anon-layer-0{ foo{} } @layer bar{ @@ -10,7 +10,7 @@ bar{} bar{} } } -@layer importted-anon-layer-1{ +@layer imported-anon-layer-1{ baz{} } @layer foobar{ diff --git a/test/layer.js b/test/layer.js index 10ff1f20..7cac7745 100644 --- a/test/layer.js +++ b/test/layer.js @@ -13,4 +13,10 @@ test( "layer-import-atrules" ) +test( + "should correctly wrap imported at rules in anonymous layers", + checkFixture, + "layer-import-atrules-anonymous" +) + test("should group rules", checkFixture, "layer-rule-grouping") From 8ed61de850f4bdc6d23c67d1e420b4f03e184b17 Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 16 Aug 2022 20:17:00 +0200 Subject: [PATCH 6/9] basic implementation --- README.md | 14 ++++++++++ index.js | 9 ++++-- ...ayer-import-atrules-anonymous.expected.css | 20 ++++++------- .../fixtures/layer-rule-grouping.expected.css | 12 ++++---- test/fixtures/layer.expected.css | 4 +-- test/layer.js | 28 ++++++++++++++++--- 6 files changed, 63 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index f7e2ac28..170e914e 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,20 @@ This option is only for adding additional directories to default resolver. If you provide your own resolver via the `resolve` configuration option above, then this value will be ignored. +#### `nameLayer` + +Type: `Function` +Default: `null` + +You can provide a custom naming function for anonymous layers (`@import 'baz.css' layer;`). +This function gets `(index, filename, importRule)` arguments and should return a unique string. + +This option only influences imports without a layer name. +Without this option the plugin will warn on anonymous layers. + +Anonymous layers are very difficult to mimic for this plugin +but they are equivalent to uniquely named layers. + #### Example with some options ```js diff --git a/index.js b/index.js index 083579a7..e8c4dcc9 100755 --- a/index.js +++ b/index.js @@ -19,6 +19,7 @@ function AtImport(options) { load: loadContent, plugins: [], addModulesDirectories: [], + nameLayer: null, ...options, } @@ -309,8 +310,12 @@ function AtImport(options) { const atRule = stmt.node const { media, layer } = stmt layer.forEach((layerPart, i) => { - if (layerPart === "") { - layer[i] = `imported-anon-layer-${state.anonymousLayerCounter++}` + if (layerPart === "" && options.nameLayer) { + layer[i] = options.nameLayer( + state.anonymousLayerCounter++, + filename, + stmt.node.toString() + ) } }) diff --git a/test/fixtures/layer-import-atrules-anonymous.expected.css b/test/fixtures/layer-import-atrules-anonymous.expected.css index 594b7afd..dbe9a33e 100644 --- a/test/fixtures/layer-import-atrules-anonymous.expected.css +++ b/test/fixtures/layer-import-atrules-anonymous.expected.css @@ -1,5 +1,5 @@ @media screen and (min-width: 320px) { -@layer imported-anon-layer-0.imported-anon-layer-1.imported-anon-layer-2 { +@layer import-anon-layer-20276dc93dc7.import-anon-layer-7e2a133c7301.import-anon-layer-fad0feff5ad9 { @layer Z { body { color: cyan; @@ -9,7 +9,7 @@ } @media screen { -@layer imported-anon-layer-0.imported-anon-layer-1 { +@layer import-anon-layer-20276dc93dc7.import-anon-layer-7e2a133c7301 { @layer Y { body { @@ -20,7 +20,7 @@ } @media screen { -@layer imported-anon-layer-0 { +@layer import-anon-layer-20276dc93dc7 { body { order: 1; @@ -29,7 +29,7 @@ body { } @media screen { -@layer imported-anon-layer-0 { +@layer import-anon-layer-20276dc93dc7 { @media (min-width: 50rem) { body { @@ -40,7 +40,7 @@ body { } @media screen { -@layer imported-anon-layer-0 { +@layer import-anon-layer-20276dc93dc7 { @keyframes RED_TO_BLUE { 0% { @@ -67,7 +67,7 @@ body { } @media (min-width: 320px) { -@layer imported-anon-layer-3.imported-anon-layer-4.imported-anon-layer-5 { +@layer import-anon-layer-247d69ad0e54.import-anon-layer-1cac8a74ff2c.import-anon-layer-33665317f72a { @layer Z { body { color: cyan; @@ -76,7 +76,7 @@ body { } } -@layer imported-anon-layer-3.imported-anon-layer-4 { +@layer import-anon-layer-247d69ad0e54.import-anon-layer-1cac8a74ff2c { @layer Y { body { @@ -85,14 +85,14 @@ body { } } -@layer imported-anon-layer-3 { +@layer import-anon-layer-247d69ad0e54 { body { order: 1; } } -@layer imported-anon-layer-3 { +@layer import-anon-layer-247d69ad0e54 { @media (min-width: 50rem) { body { @@ -101,7 +101,7 @@ body { } } -@layer imported-anon-layer-3 { +@layer import-anon-layer-247d69ad0e54 { @keyframes RED_TO_BLUE { 0% { diff --git a/test/fixtures/layer-rule-grouping.expected.css b/test/fixtures/layer-rule-grouping.expected.css index 05c0cbf5..165dd964 100644 --- a/test/fixtures/layer-rule-grouping.expected.css +++ b/test/fixtures/layer-rule-grouping.expected.css @@ -18,14 +18,14 @@ rule-five {} rule-six {} } -@layer imported-anon-layer-0 { +@layer import-anon-layer-b9a4ee60a544 { rule-one {} rule-two {} } -@layer imported-anon-layer-0 { +@layer import-anon-layer-b9a4ee60a544 { @media (min-width: 50rem) { rule-three {} @@ -34,21 +34,21 @@ rule-two {} } } -@layer imported-anon-layer-0 { +@layer import-anon-layer-b9a4ee60a544 { rule-five {} rule-six {} } -@layer imported-anon-layer-1 { +@layer import-anon-layer-c81ed089cee2 { rule-one {} rule-two {} } -@layer imported-anon-layer-1 { +@layer import-anon-layer-c81ed089cee2 { @media (min-width: 50rem) { rule-three {} @@ -57,7 +57,7 @@ rule-two {} } } -@layer imported-anon-layer-1 { +@layer import-anon-layer-c81ed089cee2 { rule-five {} diff --git a/test/fixtures/layer.expected.css b/test/fixtures/layer.expected.css index 65fa65d0..de8e4737 100644 --- a/test/fixtures/layer.expected.css +++ b/test/fixtures/layer.expected.css @@ -1,5 +1,5 @@ @layer layer-alpha, layer-beta.one; -@layer imported-anon-layer-0{ +@layer import-anon-layer-02e61fe44938{ foo{} } @layer bar{ @@ -10,7 +10,7 @@ bar{} bar{} } } -@layer imported-anon-layer-1{ +@layer import-anon-layer-c204a013b5b6{ baz{} } @layer foobar{ diff --git a/test/layer.js b/test/layer.js index 7cac7745..f04964a6 100644 --- a/test/layer.js +++ b/test/layer.js @@ -1,22 +1,42 @@ "use strict" // external tooling const test = require("ava") +const crypto = require("crypto") +const cwd = process.cwd() // internal tooling const checkFixture = require("./helpers/check-fixture") -test("should resolve layers of import statements", checkFixture, "layer") +test("should resolve layers of import statements", checkFixture, "layer", { + nameLayer: hashLayerName, +}) test( "should correctly wrap imported at rules in layers", checkFixture, - "layer-import-atrules" + "layer-import-atrules", + { + nameLayer: hashLayerName, + } ) test( "should correctly wrap imported at rules in anonymous layers", checkFixture, - "layer-import-atrules-anonymous" + "layer-import-atrules-anonymous", + { + nameLayer: hashLayerName, + } ) -test("should group rules", checkFixture, "layer-rule-grouping") +test("should group rules", checkFixture, "layer-rule-grouping", { + nameLayer: hashLayerName, +}) + +function hashLayerName(index, filename, importRule) { + return `import-anon-layer-${crypto + .createHash("sha256") + .update(`${index}-${filename.split(cwd)[1]}-${importRule}`) + .digest("hex") + .slice(0, 12)}` +} From 6cac32d5a89b5d0f385d4fe0b9037f30fa13300f Mon Sep 17 00:00:00 2001 From: romainmenke Date: Tue, 16 Aug 2022 20:53:42 +0200 Subject: [PATCH 7/9] add warning --- index.js | 19 +++++++++++++------ test/layer.js | 4 ++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index e8c4dcc9..5c973bd6 100755 --- a/index.js +++ b/index.js @@ -310,12 +310,19 @@ function AtImport(options) { const atRule = stmt.node const { media, layer } = stmt layer.forEach((layerPart, i) => { - if (layerPart === "" && options.nameLayer) { - layer[i] = options.nameLayer( - state.anonymousLayerCounter++, - filename, - stmt.node.toString() - ) + if (layerPart === "") { + if (options.nameLayer) { + layer[i] = options.nameLayer( + state.anonymousLayerCounter++, + filename, + stmt.node.toString() + ) + } else { + result.warn( + `When using anonymous layers in @import you must also set the "nameLayer" plugin option`, + { node: atRule } + ) + } } }) diff --git a/test/layer.js b/test/layer.js index f04964a6..07401324 100644 --- a/test/layer.js +++ b/test/layer.js @@ -34,6 +34,10 @@ test("should group rules", checkFixture, "layer-rule-grouping", { }) function hashLayerName(index, filename, importRule) { + // A stable, deterministic and unique layer name: + // - layer index + // - relative filename to current working directory + // - import rule source return `import-anon-layer-${crypto .createHash("sha256") .update(`${index}-${filename.split(cwd)[1]}-${importRule}`) From 5a3c4976b91bd54757bb7c84fe259413c6a2fa0b Mon Sep 17 00:00:00 2001 From: romainmenke Date: Wed, 17 Aug 2022 19:58:43 +0200 Subject: [PATCH 8/9] cleanup and more tests --- README.md | 2 +- index.js | 12 ++++--- ...ayer-import-atrules-anonymous.expected.css | 20 +++++------ .../fixtures/layer-rule-grouping.expected.css | 12 +++---- test/fixtures/layer.css | 4 +-- test/fixtures/layer.expected.css | 4 +-- test/layer.js | 35 +++++++++++++++---- 7 files changed, 57 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 170e914e..a91fa552 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Type: `Function` Default: `null` You can provide a custom naming function for anonymous layers (`@import 'baz.css' layer;`). -This function gets `(index, filename, importRule)` arguments and should return a unique string. +This function gets `(index, filename)` arguments and should return a unique string. This option only influences imports without a layer name. Without this option the plugin will warn on anonymous layers. diff --git a/index.js b/index.js index 5c973bd6..4cef88a5 100755 --- a/index.js +++ b/index.js @@ -49,6 +49,10 @@ function AtImport(options) { throw new Error("plugins option must be an array") } + if (options.nameLayer && typeof options.nameLayer !== "function") { + throw new Error("nameLayer option must be a function") + } + return parseStyles(result, styles, options, state, [], []).then( bundle => { applyRaws(bundle) @@ -312,11 +316,9 @@ function AtImport(options) { layer.forEach((layerPart, i) => { if (layerPart === "") { if (options.nameLayer) { - layer[i] = options.nameLayer( - state.anonymousLayerCounter++, - filename, - stmt.node.toString() - ) + layer[i] = options + .nameLayer(state.anonymousLayerCounter++, filename) + .toString() } else { result.warn( `When using anonymous layers in @import you must also set the "nameLayer" plugin option`, diff --git a/test/fixtures/layer-import-atrules-anonymous.expected.css b/test/fixtures/layer-import-atrules-anonymous.expected.css index dbe9a33e..622c79f6 100644 --- a/test/fixtures/layer-import-atrules-anonymous.expected.css +++ b/test/fixtures/layer-import-atrules-anonymous.expected.css @@ -1,5 +1,5 @@ @media screen and (min-width: 320px) { -@layer import-anon-layer-20276dc93dc7.import-anon-layer-7e2a133c7301.import-anon-layer-fad0feff5ad9 { +@layer import-anon-layer-ee0eb586e3cb.import-anon-layer-1c6bcc6a6840.import-anon-layer-c28eb28fcb9e { @layer Z { body { color: cyan; @@ -9,7 +9,7 @@ } @media screen { -@layer import-anon-layer-20276dc93dc7.import-anon-layer-7e2a133c7301 { +@layer import-anon-layer-ee0eb586e3cb.import-anon-layer-1c6bcc6a6840 { @layer Y { body { @@ -20,7 +20,7 @@ } @media screen { -@layer import-anon-layer-20276dc93dc7 { +@layer import-anon-layer-ee0eb586e3cb { body { order: 1; @@ -29,7 +29,7 @@ body { } @media screen { -@layer import-anon-layer-20276dc93dc7 { +@layer import-anon-layer-ee0eb586e3cb { @media (min-width: 50rem) { body { @@ -40,7 +40,7 @@ body { } @media screen { -@layer import-anon-layer-20276dc93dc7 { +@layer import-anon-layer-ee0eb586e3cb { @keyframes RED_TO_BLUE { 0% { @@ -67,7 +67,7 @@ body { } @media (min-width: 320px) { -@layer import-anon-layer-247d69ad0e54.import-anon-layer-1cac8a74ff2c.import-anon-layer-33665317f72a { +@layer import-anon-layer-8aab87a450d5.import-anon-layer-b0405b84e857.import-anon-layer-77a87e054b81 { @layer Z { body { color: cyan; @@ -76,7 +76,7 @@ body { } } -@layer import-anon-layer-247d69ad0e54.import-anon-layer-1cac8a74ff2c { +@layer import-anon-layer-8aab87a450d5.import-anon-layer-b0405b84e857 { @layer Y { body { @@ -85,14 +85,14 @@ body { } } -@layer import-anon-layer-247d69ad0e54 { +@layer import-anon-layer-8aab87a450d5 { body { order: 1; } } -@layer import-anon-layer-247d69ad0e54 { +@layer import-anon-layer-8aab87a450d5 { @media (min-width: 50rem) { body { @@ -101,7 +101,7 @@ body { } } -@layer import-anon-layer-247d69ad0e54 { +@layer import-anon-layer-8aab87a450d5 { @keyframes RED_TO_BLUE { 0% { diff --git a/test/fixtures/layer-rule-grouping.expected.css b/test/fixtures/layer-rule-grouping.expected.css index 165dd964..96aa9086 100644 --- a/test/fixtures/layer-rule-grouping.expected.css +++ b/test/fixtures/layer-rule-grouping.expected.css @@ -18,14 +18,14 @@ rule-five {} rule-six {} } -@layer import-anon-layer-b9a4ee60a544 { +@layer import-anon-layer-64359fd6446a { rule-one {} rule-two {} } -@layer import-anon-layer-b9a4ee60a544 { +@layer import-anon-layer-64359fd6446a { @media (min-width: 50rem) { rule-three {} @@ -34,21 +34,21 @@ rule-two {} } } -@layer import-anon-layer-b9a4ee60a544 { +@layer import-anon-layer-64359fd6446a { rule-five {} rule-six {} } -@layer import-anon-layer-c81ed089cee2 { +@layer import-anon-layer-47b84b25cc00 { rule-one {} rule-two {} } -@layer import-anon-layer-c81ed089cee2 { +@layer import-anon-layer-47b84b25cc00 { @media (min-width: 50rem) { rule-three {} @@ -57,7 +57,7 @@ rule-two {} } } -@layer import-anon-layer-c81ed089cee2 { +@layer import-anon-layer-47b84b25cc00 { rule-five {} diff --git a/test/fixtures/layer.css b/test/fixtures/layer.css index 1d1c59a4..b9894dab 100644 --- a/test/fixtures/layer.css +++ b/test/fixtures/layer.css @@ -1,9 +1,9 @@ @layer layer-alpha, layer-beta.one; -@import "foo.css" layer; +@import "foo.css" layer(foo); @import 'bar.css' layer(bar); @import 'bar.css' layer(bar) level-1 and level-2; -@import url(baz.css) layer; +@import url(baz.css) layer(baz); @import url("foobar.css") layer(foobar); @import url("foo-layered.css") layer(foo-layered); diff --git a/test/fixtures/layer.expected.css b/test/fixtures/layer.expected.css index de8e4737..bc73a558 100644 --- a/test/fixtures/layer.expected.css +++ b/test/fixtures/layer.expected.css @@ -1,5 +1,5 @@ @layer layer-alpha, layer-beta.one; -@layer import-anon-layer-02e61fe44938{ +@layer foo{ foo{} } @layer bar{ @@ -10,7 +10,7 @@ bar{} bar{} } } -@layer import-anon-layer-c204a013b5b6{ +@layer baz{ baz{} } @layer foobar{ diff --git a/test/layer.js b/test/layer.js index 07401324..37c9a5ae 100644 --- a/test/layer.js +++ b/test/layer.js @@ -1,15 +1,18 @@ "use strict" // external tooling const test = require("ava") +const postcss = require("postcss") + const crypto = require("crypto") const cwd = process.cwd() +// plugin +const atImport = require("..") + // internal tooling const checkFixture = require("./helpers/check-fixture") -test("should resolve layers of import statements", checkFixture, "layer", { - nameLayer: hashLayerName, -}) +test("should resolve layers of import statements", checkFixture, "layer") test( "should correctly wrap imported at rules in layers", @@ -33,14 +36,34 @@ test("should group rules", checkFixture, "layer-rule-grouping", { nameLayer: hashLayerName, }) -function hashLayerName(index, filename, importRule) { +test("should error when value is not a function", t => { + return postcss() + .use(atImport({ nameLayer: "not a function" })) + .process("", { from: undefined }) + .catch(error => t.is(error.message, "nameLayer option must be a function")) +}) + +test("should warn when using anonymous layers without the nameLayer plugin option", t => { + return postcss() + .use(atImport({ path: "test/fixtures/imports" })) + .process('@import "foo.css" layer;', { from: undefined }) + .then(result => { + const warnings = result.warnings() + t.is(warnings.length, 1) + t.is( + warnings[0].text, + 'When using anonymous layers in @import you must also set the "nameLayer" plugin option' + ) + }) +}) + +function hashLayerName(index, filename) { // A stable, deterministic and unique layer name: // - layer index // - relative filename to current working directory - // - import rule source return `import-anon-layer-${crypto .createHash("sha256") - .update(`${index}-${filename.split(cwd)[1]}-${importRule}`) + .update(`${index}-${filename.split(cwd)[1]}`) .digest("hex") .slice(0, 12)}` } From f064dc24b6124b81f3528696eb0dc551420ed81f Mon Sep 17 00:00:00 2001 From: romainmenke Date: Mon, 29 Aug 2022 19:59:07 +0200 Subject: [PATCH 9/9] use the rootFilename --- README.md | 5 +--- index.js | 9 +++--- ...ayer-import-atrules-anonymous.expected.css | 20 ++++++------- .../fixtures/layer-rule-grouping.expected.css | 12 ++++---- test/layer.js | 29 +++++++++++++------ 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a91fa552..ac0148e1 100644 --- a/README.md +++ b/README.md @@ -196,14 +196,11 @@ Type: `Function` Default: `null` You can provide a custom naming function for anonymous layers (`@import 'baz.css' layer;`). -This function gets `(index, filename)` arguments and should return a unique string. +This function gets `(index, rootFilename)` arguments and should return a unique string. This option only influences imports without a layer name. Without this option the plugin will warn on anonymous layers. -Anonymous layers are very difficult to mimic for this plugin -but they are equivalent to uniquely named layers. - #### Example with some options ```js diff --git a/index.js b/index.js index 4cef88a5..6cb912e1 100755 --- a/index.js +++ b/index.js @@ -38,10 +38,12 @@ function AtImport(options) { const state = { importedFiles: {}, hashFiles: {}, + rootFilename: null, anonymousLayerCounter: 0, } if (styles.source && styles.source.input && styles.source.input.file) { + state.rootFilename = styles.source.input.file state.importedFiles[styles.source.input.file] = {} } @@ -317,12 +319,11 @@ function AtImport(options) { if (layerPart === "") { if (options.nameLayer) { layer[i] = options - .nameLayer(state.anonymousLayerCounter++, filename) + .nameLayer(state.anonymousLayerCounter++, state.rootFilename) .toString() } else { - result.warn( - `When using anonymous layers in @import you must also set the "nameLayer" plugin option`, - { node: atRule } + throw atRule.error( + `When using anonymous layers in @import you must also set the "nameLayer" plugin option` ) } } diff --git a/test/fixtures/layer-import-atrules-anonymous.expected.css b/test/fixtures/layer-import-atrules-anonymous.expected.css index 622c79f6..be1df273 100644 --- a/test/fixtures/layer-import-atrules-anonymous.expected.css +++ b/test/fixtures/layer-import-atrules-anonymous.expected.css @@ -1,5 +1,5 @@ @media screen and (min-width: 320px) { -@layer import-anon-layer-ee0eb586e3cb.import-anon-layer-1c6bcc6a6840.import-anon-layer-c28eb28fcb9e { +@layer import-anon-layer-0.import-anon-layer-1.import-anon-layer-2 { @layer Z { body { color: cyan; @@ -9,7 +9,7 @@ } @media screen { -@layer import-anon-layer-ee0eb586e3cb.import-anon-layer-1c6bcc6a6840 { +@layer import-anon-layer-0.import-anon-layer-1 { @layer Y { body { @@ -20,7 +20,7 @@ } @media screen { -@layer import-anon-layer-ee0eb586e3cb { +@layer import-anon-layer-0 { body { order: 1; @@ -29,7 +29,7 @@ body { } @media screen { -@layer import-anon-layer-ee0eb586e3cb { +@layer import-anon-layer-0 { @media (min-width: 50rem) { body { @@ -40,7 +40,7 @@ body { } @media screen { -@layer import-anon-layer-ee0eb586e3cb { +@layer import-anon-layer-0 { @keyframes RED_TO_BLUE { 0% { @@ -67,7 +67,7 @@ body { } @media (min-width: 320px) { -@layer import-anon-layer-8aab87a450d5.import-anon-layer-b0405b84e857.import-anon-layer-77a87e054b81 { +@layer import-anon-layer-3.import-anon-layer-4.import-anon-layer-5 { @layer Z { body { color: cyan; @@ -76,7 +76,7 @@ body { } } -@layer import-anon-layer-8aab87a450d5.import-anon-layer-b0405b84e857 { +@layer import-anon-layer-3.import-anon-layer-4 { @layer Y { body { @@ -85,14 +85,14 @@ body { } } -@layer import-anon-layer-8aab87a450d5 { +@layer import-anon-layer-3 { body { order: 1; } } -@layer import-anon-layer-8aab87a450d5 { +@layer import-anon-layer-3 { @media (min-width: 50rem) { body { @@ -101,7 +101,7 @@ body { } } -@layer import-anon-layer-8aab87a450d5 { +@layer import-anon-layer-3 { @keyframes RED_TO_BLUE { 0% { diff --git a/test/fixtures/layer-rule-grouping.expected.css b/test/fixtures/layer-rule-grouping.expected.css index 96aa9086..a4e34d0e 100644 --- a/test/fixtures/layer-rule-grouping.expected.css +++ b/test/fixtures/layer-rule-grouping.expected.css @@ -18,14 +18,14 @@ rule-five {} rule-six {} } -@layer import-anon-layer-64359fd6446a { +@layer import-anon-layer-0 { rule-one {} rule-two {} } -@layer import-anon-layer-64359fd6446a { +@layer import-anon-layer-0 { @media (min-width: 50rem) { rule-three {} @@ -34,21 +34,21 @@ rule-two {} } } -@layer import-anon-layer-64359fd6446a { +@layer import-anon-layer-0 { rule-five {} rule-six {} } -@layer import-anon-layer-47b84b25cc00 { +@layer import-anon-layer-1 { rule-one {} rule-two {} } -@layer import-anon-layer-47b84b25cc00 { +@layer import-anon-layer-1 { @media (min-width: 50rem) { rule-three {} @@ -57,7 +57,7 @@ rule-two {} } } -@layer import-anon-layer-47b84b25cc00 { +@layer import-anon-layer-1 { rule-five {} diff --git a/test/layer.js b/test/layer.js index 37c9a5ae..69d27afd 100644 --- a/test/layer.js +++ b/test/layer.js @@ -36,6 +36,15 @@ test("should group rules", checkFixture, "layer-rule-grouping", { nameLayer: hashLayerName, }) +test("should pass the root file name to the nameLayer function", t => { + return postcss() + .use(atImport({ path: "test/fixtures/imports", nameLayer: hashLayerName })) + .process('@import "foo.css" layer;', { from: "layer.css" }) + .then(result => { + t.is(result.css, `@layer import-anon-layer-52ff1597784c{\nfoo{}\n}`) + }) +}) + test("should error when value is not a function", t => { return postcss() .use(atImport({ nameLayer: "not a function" })) @@ -43,27 +52,29 @@ test("should error when value is not a function", t => { .catch(error => t.is(error.message, "nameLayer option must be a function")) }) -test("should warn when using anonymous layers without the nameLayer plugin option", t => { +test("should throw when using anonymous layers without the nameLayer plugin option", t => { return postcss() .use(atImport({ path: "test/fixtures/imports" })) .process('@import "foo.css" layer;', { from: undefined }) - .then(result => { - const warnings = result.warnings() - t.is(warnings.length, 1) + .catch(err => { t.is( - warnings[0].text, - 'When using anonymous layers in @import you must also set the "nameLayer" plugin option' + err.message, + 'postcss-import: :1:1: When using anonymous layers in @import you must also set the "nameLayer" plugin option' ) }) }) -function hashLayerName(index, filename) { +function hashLayerName(index, rootFilename) { + if (!rootFilename) { + return `import-anon-layer-${index}` + } + // A stable, deterministic and unique layer name: // - layer index - // - relative filename to current working directory + // - relative rootFilename to current working directory return `import-anon-layer-${crypto .createHash("sha256") - .update(`${index}-${filename.split(cwd)[1]}`) + .update(`${index}-${rootFilename.split(cwd)[1]}`) .digest("hex") .slice(0, 12)}` }