Skip to content

Commit c262a30

Browse files
authored
implement supports conditions (#548)
* implement supports conditions * apply suggestion from code review * media combine * layer : add dedicated test for duplicate anonymous imports
1 parent 3d51fe5 commit c262a30

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+390
-486
lines changed

README.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,17 +202,6 @@ This option is only for adding additional directories to default resolver. If
202202
you provide your own resolver via the `resolve` configuration option above, then
203203
this value will be ignored.
204204

205-
#### `nameLayer`
206-
207-
Type: `Function`
208-
Default: `null`
209-
210-
You can provide a custom naming function for anonymous layers (`@import 'baz.css' layer;`).
211-
This function gets `(index, rootFilename)` arguments and should return a unique string.
212-
213-
This option only influences imports without a layer name.
214-
Without this option the plugin will warn on anonymous layers.
215-
216205
#### `warnOnEmpty`
217206

218207
Type: `Boolean`

index.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const path = require("path")
44

55
// internal tooling
6-
const applyMedia = require("./lib/apply-media")
6+
const applyConditions = require("./lib/apply-conditions")
77
const applyRaws = require("./lib/apply-raws")
88
const applyStyles = require("./lib/apply-styles")
99
const loadContent = require("./lib/load-content")
@@ -19,7 +19,6 @@ function AtImport(options) {
1919
load: loadContent,
2020
plugins: [],
2121
addModulesDirectories: [],
22-
nameLayer: null,
2322
warnOnEmpty: true,
2423
...options,
2524
}
@@ -39,36 +38,28 @@ function AtImport(options) {
3938
const state = {
4039
importedFiles: {},
4140
hashFiles: {},
42-
rootFilename: null,
43-
anonymousLayerCounter: 0,
4441
}
4542

4643
if (styles.source?.input?.file) {
47-
state.rootFilename = styles.source.input.file
4844
state.importedFiles[styles.source.input.file] = {}
4945
}
5046

5147
if (options.plugins && !Array.isArray(options.plugins)) {
5248
throw new Error("plugins option must be an array")
5349
}
5450

55-
if (options.nameLayer && typeof options.nameLayer !== "function") {
56-
throw new Error("nameLayer option must be a function")
57-
}
58-
5951
const bundle = await parseStyles(
6052
result,
6153
styles,
6254
options,
6355
state,
6456
[],
6557
[],
66-
[],
6758
postcss
6859
)
6960

7061
applyRaws(bundle)
71-
applyMedia(bundle, options, state, atRule)
62+
applyConditions(bundle, atRule)
7263
applyStyles(bundle, styles)
7364
},
7465
}

lib/apply-conditions.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"use strict"
2+
3+
const base64EncodedConditionalImport = require("./base64-encoded-import")
4+
5+
module.exports = function applyConditions(bundle, atRule) {
6+
bundle.forEach(stmt => {
7+
if (
8+
stmt.type === "charset" ||
9+
stmt.type === "warning" ||
10+
!stmt.conditions?.length
11+
) {
12+
return
13+
}
14+
15+
if (stmt.type === "import") {
16+
stmt.node.params = base64EncodedConditionalImport(
17+
stmt.fullUri,
18+
stmt.conditions
19+
)
20+
return
21+
}
22+
23+
const { nodes } = stmt
24+
const { parent } = nodes[0]
25+
26+
const atRules = []
27+
28+
// Convert conditions to at-rules
29+
for (const condition of stmt.conditions) {
30+
if (typeof condition.media !== "undefined") {
31+
const mediaNode = atRule({
32+
name: "media",
33+
params: condition.media,
34+
source: parent.source,
35+
})
36+
37+
atRules.push(mediaNode)
38+
}
39+
40+
if (typeof condition.supports !== "undefined") {
41+
const supportsNode = atRule({
42+
name: "supports",
43+
params: `(${condition.supports})`,
44+
source: parent.source,
45+
})
46+
47+
atRules.push(supportsNode)
48+
}
49+
50+
if (typeof condition.layer !== "undefined") {
51+
const layerNode = atRule({
52+
name: "layer",
53+
params: condition.layer,
54+
source: parent.source,
55+
})
56+
57+
atRules.push(layerNode)
58+
}
59+
}
60+
61+
// Add nodes to AST
62+
const outerAtRule = atRules.shift()
63+
const innerAtRule = atRules.reduce((previous, next) => {
64+
previous.append(next)
65+
return next
66+
}, outerAtRule)
67+
68+
parent.insertBefore(nodes[0], outerAtRule)
69+
70+
// remove nodes
71+
nodes.forEach(node => {
72+
node.parent = undefined
73+
})
74+
75+
// better output
76+
nodes[0].raws.before = nodes[0].raws.before || "\n"
77+
78+
// wrap new rules with media query and/or layer at rule
79+
innerAtRule.append(nodes)
80+
81+
stmt.type = "nodes"
82+
stmt.nodes = [outerAtRule]
83+
delete stmt.node
84+
})
85+
}

lib/apply-media.js

Lines changed: 0 additions & 113 deletions
This file was deleted.

lib/apply-styles.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module.exports = function applyStyles(bundle, styles) {
55

66
// Strip additional statements.
77
bundle.forEach(stmt => {
8-
if (["charset", "import", "media"].includes(stmt.type)) {
8+
if (["charset", "import"].includes(stmt.type)) {
99
stmt.node.parent = undefined
1010
styles.append(stmt.node)
1111
} else if (stmt.type === "nodes") {

lib/assign-layer-names.js

Lines changed: 0 additions & 17 deletions
This file was deleted.

lib/base64-encoded-import.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
"use strict"
2+
3+
const formatImportPrelude = require("./format-import-prelude")
4+
5+
// Base64 encode an import with conditions
6+
// The order of conditions is important and is interleaved with cascade layer declarations
7+
// Each group of conditions and cascade layers needs to be interpreted in order
8+
// To achieve this we create a list of base64 encoded imports, where each import contains a stylesheet with another import.
9+
// Each import can define a single group of conditions and a single cascade layer.
10+
module.exports = function base64EncodedConditionalImport(prelude, conditions) {
11+
conditions.reverse()
12+
const first = conditions.pop()
13+
let params = `${prelude} ${formatImportPrelude(
14+
first.layer,
15+
first.media,
16+
first.supports
17+
)}`
18+
19+
for (const condition of conditions) {
20+
params = `'data:text/css;base64,${Buffer.from(`@import ${params}`).toString(
21+
"base64"
22+
)}' ${formatImportPrelude(
23+
condition.layer,
24+
condition.media,
25+
condition.supports
26+
)}`
27+
}
28+
29+
return params
30+
}

lib/format-import-prelude.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"use strict"
2+
3+
module.exports = function formatImportPrelude(layer, media, supports) {
4+
const parts = []
5+
6+
if (typeof layer !== "undefined") {
7+
let layerParams = "layer"
8+
if (layer) {
9+
layerParams = `layer(${layer})`
10+
}
11+
12+
parts.push(layerParams)
13+
}
14+
15+
if (typeof supports !== "undefined") {
16+
parts.push(`supports(${supports})`)
17+
}
18+
19+
if (typeof media !== "undefined") {
20+
parts.push(media)
21+
}
22+
23+
return parts.join(" ")
24+
}

lib/join-layer.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

lib/join-media.js

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)