Skip to content

Commit

Permalink
implement supports conditions (#548)
Browse files Browse the repository at this point in the history
* implement supports conditions

* apply suggestion from code review

* media combine

* layer : add dedicated test for duplicate anonymous imports
  • Loading branch information
romainmenke committed Dec 28, 2023
1 parent 3d51fe5 commit c262a30
Show file tree
Hide file tree
Showing 43 changed files with 390 additions and 486 deletions.
11 changes: 0 additions & 11 deletions README.md
Expand Up @@ -202,17 +202,6 @@ 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, 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.

#### `warnOnEmpty`

Type: `Boolean`
Expand Down
13 changes: 2 additions & 11 deletions index.js
Expand Up @@ -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")
Expand All @@ -19,7 +19,6 @@ function AtImport(options) {
load: loadContent,
plugins: [],
addModulesDirectories: [],
nameLayer: null,
warnOnEmpty: true,
...options,
}
Expand All @@ -39,36 +38,28 @@ function AtImport(options) {
const state = {
importedFiles: {},
hashFiles: {},
rootFilename: null,
anonymousLayerCounter: 0,
}

if (styles.source?.input?.file) {
state.rootFilename = styles.source.input.file
state.importedFiles[styles.source.input.file] = {}
}

if (options.plugins && !Array.isArray(options.plugins)) {
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")
}

const bundle = await parseStyles(
result,
styles,
options,
state,
[],
[],
[],
postcss
)

applyRaws(bundle)
applyMedia(bundle, options, state, atRule)
applyConditions(bundle, atRule)
applyStyles(bundle, styles)
},
}
Expand Down
85 changes: 85 additions & 0 deletions lib/apply-conditions.js
@@ -0,0 +1,85 @@
"use strict"

const base64EncodedConditionalImport = require("./base64-encoded-import")

module.exports = function applyConditions(bundle, atRule) {
bundle.forEach(stmt => {
if (
stmt.type === "charset" ||
stmt.type === "warning" ||
!stmt.conditions?.length
) {
return
}

if (stmt.type === "import") {
stmt.node.params = base64EncodedConditionalImport(
stmt.fullUri,
stmt.conditions
)
return
}

const { nodes } = stmt
const { parent } = nodes[0]

const atRules = []

// Convert conditions to at-rules
for (const condition of stmt.conditions) {
if (typeof condition.media !== "undefined") {
const mediaNode = atRule({
name: "media",
params: condition.media,
source: parent.source,
})

atRules.push(mediaNode)
}

if (typeof condition.supports !== "undefined") {
const supportsNode = atRule({
name: "supports",
params: `(${condition.supports})`,
source: parent.source,
})

atRules.push(supportsNode)
}

if (typeof condition.layer !== "undefined") {
const layerNode = atRule({
name: "layer",
params: condition.layer,
source: parent.source,
})

atRules.push(layerNode)
}
}

// Add nodes to AST
const outerAtRule = atRules.shift()
const innerAtRule = atRules.reduce((previous, next) => {
previous.append(next)
return next
}, outerAtRule)

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
})
}
113 changes: 0 additions & 113 deletions lib/apply-media.js

This file was deleted.

2 changes: 1 addition & 1 deletion lib/apply-styles.js
Expand Up @@ -5,7 +5,7 @@ module.exports = function applyStyles(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") {
Expand Down
17 changes: 0 additions & 17 deletions lib/assign-layer-names.js

This file was deleted.

30 changes: 30 additions & 0 deletions lib/base64-encoded-import.js
@@ -0,0 +1,30 @@
"use strict"

const formatImportPrelude = require("./format-import-prelude")

// Base64 encode an import with conditions
// The order of conditions is important and is interleaved with cascade layer declarations
// Each group of conditions and cascade layers needs to be interpreted in order
// 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) {
conditions.reverse()
const first = conditions.pop()
let params = `${prelude} ${formatImportPrelude(
first.layer,
first.media,
first.supports
)}`

for (const condition of conditions) {
params = `'data:text/css;base64,${Buffer.from(`@import ${params}`).toString(
"base64"
)}' ${formatImportPrelude(
condition.layer,
condition.media,
condition.supports
)}`
}

return params
}
24 changes: 24 additions & 0 deletions lib/format-import-prelude.js
@@ -0,0 +1,24 @@
"use strict"

module.exports = function formatImportPrelude(layer, media, supports) {
const parts = []

if (typeof layer !== "undefined") {
let layerParams = "layer"
if (layer) {
layerParams = `layer(${layer})`
}

parts.push(layerParams)
}

if (typeof supports !== "undefined") {
parts.push(`supports(${supports})`)
}

if (typeof media !== "undefined") {
parts.push(media)
}

return parts.join(" ")
}
9 changes: 0 additions & 9 deletions lib/join-layer.js

This file was deleted.

30 changes: 0 additions & 30 deletions lib/join-media.js

This file was deleted.

0 comments on commit c262a30

Please sign in to comment.