Socket
Socket
Sign inDemoInstall

postcss-nested

Package Overview
Dependencies
7
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.0.6 to 6.0.0

11

index.d.ts

@@ -9,4 +9,4 @@ // Original definitions (@types/postcss-nested)

/**
* By default, plugin will bubble only `@media` and `@supports` at-rules.
* You can add your custom at-rules to this list by this option.
* By default, plugin will bubble only `@media`, `@supports` and `@layer`
* at-rules. Use this option to add your custom at-rules to this list.
*/

@@ -28,2 +28,9 @@ bubble?: string[]

preserveEmpty?: boolean
/**
* The plugin supports the SCSS custom at-rule `@at-root` which breaks
* rule blocks out of their nested position. If you want, you can choose
* a new custom name for this rule in your code.
*/
rootRuleName?: string
}

@@ -30,0 +37,0 @@

316

index.js

@@ -0,12 +1,15 @@

const { Rule, AtRule } = require('postcss')
let parser = require('postcss-selector-parser')
function parse (str, rule) {
/**
* Run a selector string through postcss-selector-parser
*/
function parse(rawSelector, rule) {
let nodes
let saver = parser(parsed => {
nodes = parsed
})
try {
saver.processSync(str)
parser(parsed => {
nodes = parsed
}).processSync(rawSelector)
} catch (e) {
if (str.includes(':')) {
if (rawSelector.includes(':')) {
throw rule ? rule.error('Missed semicolon') : e

@@ -20,15 +23,23 @@ } else {

function replace (nodes, parent) {
/**
* Replaces the "&" token in a node's selector with the parent selector
* similar to what SCSS does.
*
* Mutates the nodes list
*/
function interpolateAmpInSelector(nodes, parent) {
let replaced = false
nodes.each(i => {
if (i.type === 'nesting') {
let clonedParent = parent.clone()
if (i.value !== '&') {
i.replaceWith(parse(i.value.replace('&', clonedParent.toString())))
nodes.each(node => {
if (node.type === 'nesting') {
let clonedParent = parent.clone({})
if (node.value !== '&') {
node.replaceWith(
parse(node.value.replace('&', clonedParent.toString()))
)
} else {
i.replaceWith(clonedParent)
node.replaceWith(clonedParent)
}
replaced = true
} else if (i.nodes) {
if (replace(i, parent)) {
} else if ('nodes' in node && node.nodes) {
if (interpolateAmpInSelector(node, parent)) {
replaced = true

@@ -41,47 +52,56 @@ }

function selectors (parent, child) {
let result = []
parent.selectors.forEach(i => {
let parentNode = parse(i, parent)
/**
* Combines parent and child selectors, in a SCSS-like way
*/
function mergeSelectors(parent, child) {
let merged = []
parent.selectors.forEach(sel => {
let parentNode = parse(sel, parent)
child.selectors.forEach(j => {
if (j.length) {
let node = parse(j, child)
let replaced = replace(node, parentNode)
if (!replaced) {
node.prepend(parser.combinator({ value: ' ' }))
node.prepend(parentNode.clone())
}
result.push(node.toString())
child.selectors.forEach(selector => {
if (!selector) {
return
}
let node = parse(selector, child)
let replaced = interpolateAmpInSelector(node, parentNode)
if (!replaced) {
node.prepend(parser.combinator({ value: ' ' }))
node.prepend(parentNode.clone({}))
}
merged.push(node.toString())
})
})
return result
return merged
}
function pickComment (comment, after) {
if (comment && comment.type === 'comment') {
after.after(comment)
return comment
} else {
return after
/**
* Move a child and its preceeding comment(s) to after "after"
*/
function breakOut(child, after) {
let prev = child.prev()
after.after(child)
while (prev && prev.type === 'comment') {
let nextPrev = prev.prev()
after.after(prev)
prev = nextPrev
}
return child
}
function createFnAtruleChilds (bubble) {
return function atruleChilds (rule, atrule, bubbling) {
function createFnAtruleChilds(bubble) {
return function atruleChilds(rule, atrule, bubbling, mergeSels = bubbling) {
let children = []
atrule.each(child => {
if (child.type === 'comment') {
children.push(child)
} else if (child.type === 'decl') {
children.push(child)
} else if (child.type === 'rule' && bubbling) {
child.selectors = selectors(rule, child)
} else if (child.type === 'atrule') {
if (child.nodes && bubble[child.name]) {
atruleChilds(rule, child, true)
} else {
if (child.type === 'rule' && bubbling) {
if (mergeSels) {
child.selectors = mergeSelectors(rule, child)
}
} else if (child.type === 'atrule' && child.nodes) {
if (bubble[child.name]) {
atruleChilds(rule, child, mergeSels)
} else if (atrule[rootRuleMergeSel] !== false) {
children.push(child)
}
} else {
children.push(child)
}

@@ -101,3 +121,3 @@ })

function pickDeclarations (selector, declarations, after, Rule) {
function pickDeclarations(selector, declarations, after) {
let parent = new Rule({

@@ -107,7 +127,3 @@ selector,

})
for (let declaration of declarations) {
parent.append(declaration)
}
parent.append(declarations)
after.after(parent)

@@ -117,11 +133,10 @@ return parent

function atruleNames (defaults, custom) {
function atruleNames(defaults, custom) {
let list = {}
for (let i of defaults) {
list[i] = true
for (let name of defaults) {
list[name] = true
}
if (custom) {
for (let i of custom) {
let name = i.replace(/^@/, '')
list[name] = true
for (let name of custom) {
list[name.replace(/^@/, '')] = true
}

@@ -132,4 +147,132 @@ }

function parseRootRuleParams(params) {
params = params.trim()
let braceBlock = params.match(/^\((.*)\)$/)
if (!braceBlock) {
return { type: 'basic', selector: params }
}
let bits = braceBlock[1].match(/^(with(?:out)?):(.+)$/)
if (bits) {
let allowlist = bits[1] === 'with'
let rules = Object.fromEntries(
bits[2]
.trim()
.split(/\s+/)
.map(name => [name, true])
)
if (allowlist && rules.all) {
return { type: 'noop' }
}
let escapes = rule => !!rules[rule]
if (rules.all) {
escapes = () => true
} else if (allowlist) {
escapes = rule => (rule === 'all' ? false : !rules[rule])
}
return {
type: 'withrules',
escapes
}
}
// Unrecognized brace block
return { type: 'unknown' }
}
function getAncestorRules(leaf) {
let lineage = []
let parent = leaf.parent
while (parent && parent instanceof AtRule) {
lineage.push(parent)
parent = parent.parent
}
return lineage
}
function unwrapRootRule(rule) {
let escapes = rule[rootRuleEscapes]
if (!escapes) {
rule.after(rule.nodes)
} else {
let nodes = rule.nodes
let topEscaped
let topEscapedIdx = -1
let breakoutLeaf
let breakoutRoot
let clone
let lineage = getAncestorRules(rule)
lineage.forEach((parent, i) => {
if (escapes(parent.name)) {
topEscaped = parent
topEscapedIdx = i
breakoutRoot = clone
} else {
let oldClone = clone
clone = parent.clone({ nodes: [] })
oldClone && clone.append(oldClone)
breakoutLeaf = breakoutLeaf || clone
}
})
if (!topEscaped) {
rule.after(nodes)
} else if (!breakoutRoot) {
topEscaped.after(nodes)
} else {
let leaf = breakoutLeaf
leaf.append(nodes)
topEscaped.after(breakoutRoot)
}
if (rule.next() && topEscaped) {
let restRoot
lineage.slice(0, topEscapedIdx + 1).forEach((parent, i, arr) => {
let oldRoot = restRoot
restRoot = parent.clone({ nodes: [] })
oldRoot && restRoot.append(oldRoot)
let nextSibs = []
let _child = arr[i - 1] || rule
let next = _child.next()
while (next) {
nextSibs.push(next)
next = next.next()
}
restRoot.append(nextSibs)
})
restRoot && (breakoutRoot || nodes[nodes.length - 1]).after(restRoot)
}
}
rule.remove()
}
const rootRuleMergeSel = Symbol('rootRuleMergeSel')
const rootRuleEscapes = Symbol('rootRuleEscapes')
function normalizeRootRule(rule) {
let { params } = rule
let { type, selector, escapes } = parseRootRuleParams(params)
if (type === 'unknown') {
throw rule.error(
`Unknown @${rule.name} parameter ${JSON.stringify(params)}`
)
}
if (type === 'basic' && selector) {
let selectorBlock = new Rule({ selector, nodes: rule.nodes })
rule.removeAll()
rule.append(selectorBlock)
}
rule[rootRuleEscapes] = escapes
rule[rootRuleMergeSel] = escapes ? !escapes('all') : type === 'noop'
}
const hasRootRule = Symbol('hasRootRule')
module.exports = (opts = {}) => {
let bubble = atruleNames(['media', 'supports'], opts.bubble)
let bubble = atruleNames(['media', 'supports', 'layer'], opts.bubble)
let atruleChilds = createFnAtruleChilds(bubble)

@@ -146,2 +289,3 @@ let unwrap = atruleNames(

)
let rootRuleName = (opts.rootRuleName || 'at-root').replace(/^@/, '')
let preserveEmpty = opts.preserveEmpty

@@ -151,3 +295,11 @@

postcssPlugin: 'postcss-nested',
Rule (rule, { Rule }) {
Once(root) {
root.walkAtRules(rootRuleName, node => {
normalizeRootRule(node)
root[hasRootRule] = true
})
},
Rule(rule) {
let unwrapped = false

@@ -161,3 +313,3 @@ let after = rule

if (declarations.length) {
after = pickDeclarations(rule.selector, declarations, after, Rule)
after = pickDeclarations(rule.selector, declarations, after)
declarations = []

@@ -168,24 +320,13 @@ }

unwrapped = true
child.selectors = selectors(rule, child)
after = pickComment(child.prev(), after)
after.after(child)
after = child
child.selectors = mergeSelectors(rule, child)
after = breakOut(child, after)
} else if (child.type === 'atrule') {
if (declarations.length) {
after = pickDeclarations(rule.selector, declarations, after, Rule)
after = pickDeclarations(rule.selector, declarations, after)
declarations = []
}
if (child.name === 'at-root') {
if (child.name === rootRuleName) {
unwrapped = true
atruleChilds(rule, child, false)
let nodes = child.nodes
if (child.params) {
nodes = new Rule({ selector: child.params, nodes })
}
after.after(nodes)
after = nodes
child.remove()
atruleChilds(rule, child, true, child[rootRuleMergeSel])
after = breakOut(child, after)
} else if (bubble[child.name]) {

@@ -195,5 +336,3 @@ copyDeclarations = true

atruleChilds(rule, child, true)
after = pickComment(child.prev(), after)
after.after(child)
after = child
after = breakOut(child, after)
} else if (unwrap[child.name]) {

@@ -203,5 +342,3 @@ copyDeclarations = true

atruleChilds(rule, child, false)
after = pickComment(child.prev(), after)
after.after(child)
after = child
after = breakOut(child, after)
} else if (copyDeclarations) {

@@ -216,3 +353,3 @@ declarations.push(child)

if (declarations.length) {
after = pickDeclarations(rule.selector, declarations, after, Rule)
after = pickDeclarations(rule.selector, declarations, after)
}

@@ -224,2 +361,9 @@

}
},
RootExit(root) {
if (root[hasRootRule]) {
root.walkAtRules(rootRuleName, unwrapRootRule)
root[hasRootRule] = false
}
}

@@ -226,0 +370,0 @@ }

{
"name": "postcss-nested",
"version": "5.0.6",
"version": "6.0.0",
"description": "PostCSS plugin to unwrap nested rules like how Sass does it",

@@ -26,4 +26,4 @@ "keywords": [

"dependencies": {
"postcss-selector-parser": "^6.0.6"
"postcss-selector-parser": "^6.0.10"
}
}

@@ -84,3 +84,3 @@ # PostCSS Nested

[`postcss-nested-props`]: https://github.com/jedmao/postcss-nested-props
[`postcss-nesting`]: https://github.com/jonathantneal/postcss-nesting
[`postcss-nesting`]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting
[CSSWG draft]: https://drafts.csswg.org/css-nesting-1/

@@ -90,111 +90,3 @@ [PostCSS]: https://github.com/postcss/postcss

## Usage
**Step 1:** Install plugin:
```sh
npm install --save-dev postcss postcss-nested
```
**Step 2:** Check your project for existing PostCSS config: `postcss.config.js`
in the project root, `"postcss"` section in `package.json`
or `postcss` in bundle config.
If you do not use PostCSS, add it according to [official docs]
and set this plugin in settings.
**Step 3:** Add the plugin to plugins list:
```diff
module.exports = {
plugins: [
+ require('postcss-nested'),
require('autoprefixer')
]
}
```
[official docs]: https://github.com/postcss/postcss#usage
## Options
### `bubble`
By default, plugin will bubble only `@media` and `@supports` at-rules.
You can add your custom at-rules to this list by `bubble` option:
```js
postcss([ require('postcss-nested')({ bubble: ['phone'] }) ])
```
```css
/* input */
a {
color: white;
@phone {
color: black;
}
}
/* output */
a {
color: white;
}
@phone {
a {
color: black;
}
}
```
### `unwrap`
By default, plugin will unwrap only `@font-face`, `@keyframes` and `@document`
at-rules. You can add your custom at-rules to this list by `unwrap` option:
```js
postcss([ require('postcss-nested')({ unwrap: ['phone'] }) ])
```
```css
/* input */
a {
color: white;
@phone {
color: black;
}
}
/* output */
a {
color: white;
}
@phone {
color: black;
}
```
### `preserveEmpty`
By default, plugin will strip out any empty selector generated by intermediate
nesting levels. You can set `preserveEmpty` to `true` to preserve them.
```css
.a {
.b {
color: black;
}
}
```
Will be compiled to:
```css
.a { }
.a .b {
color: black;
}
```
This is especially useful if you want to export the empty classes with `postcss-modules`.
## Docs
Read **[full docs](https://github.com/postcss/postcss-nested#readme)** on GitHub.
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc