Skip to content

Migrated to postcss 8; add ability to use multiple namespaces #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Installation

```bash
$ npm install postcss-selector-namespace
$ npm i postcss postcss-selector-namespace
```

## Usage
Expand All @@ -13,7 +13,7 @@ var postcss = require('postcss')
var selectorNamespace = require('postcss-selector-namespace')

var output = postcss()
.use(selectorNamespace({ selfSelector: ':--component', namespace: 'my-component' }))
.use(selectorNamespace({ selfSelector: ':--component', namespace: '.my-component' }))
.process(require('fs').readFileSync('input.css', 'utf8'))
.css
```
Expand Down Expand Up @@ -65,6 +65,49 @@ h1 {
}
```

### Adding multiple namespaces is also supported.

```javascript
var postcss = require('postcss')
var selectorNamespace = require('postcss-selector-namespace')

var output = postcss()
.use(selectorNamespace({ selfSelector: ':--component', namespace: '.foo, .bar' }))
.process(require('fs').readFileSync('input.css', 'utf8'))
.css
```

`input.css`
```css
:--component {
color: black;
}

:--component.danger {
color: red;
}

h1, .h1 {
font-weight: bold;
}
```

will output the following css:

```css
.foo, .bar {
color: black;
}

.foo.danger, .bar.danger {
color: red;
}

.foo h1, .bar .h1 {
font-weight: bold;
}
```

## SCSS support

This plugin can also process scss files and output scss again using the
Expand Down Expand Up @@ -228,7 +271,7 @@ body {

(default: `'.self'`)

The selector to prepend to each selector.
The selector(s) to prepend to each selector.

### `selfSelector`

Expand Down
170 changes: 85 additions & 85 deletions lib/plugin.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,20 @@
import postcss from 'postcss'

module.exports = postcss.plugin(
'postcss-selector-namespace',
(options = {}) => {
let {
namespace = '.self',
selfSelector = /:--namespace/,
rootSelector = /:root/,
ignoreRoot = true,
dropRoot = true,
} = options

selfSelector = regexpToGlobalRegexp(selfSelector)

return (css, result) => {
const computedNamespace =
typeof namespace === 'string'
? namespace
: namespace(css.source.input.file)

if (!computedNamespace) {
return
}

css.walkRules(rule => {
if (canNamespaceSelectors(rule)) {
return
}

rule.selectors = rule.selectors.map(selector =>
namespaceSelector(selector, computedNamespace),
)
})
}

function namespaceSelector(selector, computedNamespace) {
if (hasSelfSelector(selector)) {
return selector.replace(selfSelector, computedNamespace)
}

if (hasRootSelector(selector)) {
return dropRootSelector(selector)
}

return `${computedNamespace} ${selector}`
}

function hasSelfSelector(selector) {
selfSelector.lastIndex = 0

return selfSelector.test(selector)
}

function hasRootSelector(selector) {
return ignoreRoot && selector.search(rootSelector) === 0
}

function dropRootSelector(selector) {
if (dropRoot) {
return selector.replace(rootSelector, '').trim() || selector
}

return selector
}
},
)

/**
* Returns true if the rule selectors can be namespaces
*
* @param {postcss.Rule} rule The rule to check
* @return {boolean} whether the rule selectors can be namespaced or not
*/
function canNamespaceSelectors(rule) {
const canNamespaceSelectors = (rule) => {
return hasParentRule(rule) || parentIsAllowedAtRule(rule)
}

/**
* Returns true if the parent rule is a not a media or supports atrule
*
* @param {postcss.Rule} rule The rule to check
* @return {boolean} true if the direct parent is a keyframe rule
*/
function parentIsAllowedAtRule(rule) {
return (
rule.parent &&
rule.parent.type === 'atrule' &&
!/(?:media|supports|for)$/.test(rule.parent.name)
)
}

/**
* Returns true if any parent rule is of type 'rule'
*
* @param {postcss.Rule|postcss.Root|postcss.AtRule} rule The rule to check
* @return {boolean} true if any parent rule is of type 'rule' else false
*/
function hasParentRule(rule) {
const hasParentRule = (rule) => {
if (!rule.parent) {
return false
}
Expand All @@ -108,6 +26,20 @@ function hasParentRule(rule) {
return hasParentRule(rule.parent)
}

/**
* Returns true if the parent rule is a not a media or supports atrule
*
* @param {postcss.Rule} rule The rule to check
* @return {boolean} true if the direct parent is a keyframe rule
*/
const parentIsAllowedAtRule = (rule) => {
return (
rule.parent &&
rule.parent.type === 'atrule' &&
!/(?:media|supports|for)$/.test(rule.parent.name)
)
}

/**
* Newer javascript engines allow setting flags when passing existing regexp
* to the RegExp constructor, until then, we extract the regexp source and
Expand All @@ -116,8 +48,76 @@ function hasParentRule(rule) {
* @param {RegExp|string} regexp The regexp to modify
* @return {RegExp} The new regexp instance
*/
function regexpToGlobalRegexp(regexp) {
const regexpToGlobalRegexp = (regexp) => {
let source = regexp instanceof RegExp ? regexp.source : regexp

return new RegExp(source, 'g')
}

module.exports = (options = {}) => {
let {
namespace = '.self',
selfSelector = /:--namespace/,
rootSelector = /:root/,
ignoreRoot = true,
dropRoot = true,
} = options

selfSelector = regexpToGlobalRegexp(selfSelector)

const namespaceSelector = (selector, computedNamespace) => {
if (hasSelfSelector(selector)) {
return computedNamespace.split(',').map(namespace => selector.replace(selfSelector, namespace)).join(',');
}

if (hasRootSelector(selector)) {
return dropRootSelector(selector)
}

return computedNamespace.split(',').map(namespace => `${namespace} ${selector}`).join(',');
}

const hasSelfSelector = (selector) => {
selfSelector.lastIndex = 0

return selfSelector.test(selector)
}

const hasRootSelector = (selector) => {
return ignoreRoot && selector.search(rootSelector) === 0
}

const dropRootSelector = (selector) => {
if (dropRoot) {
return selector.replace(rootSelector, '').trim() || selector
}

return selector
}

return {
postcssPlugin: 'postcss-selector-namespaces',
Once (root, { result }) {
const computedNamespace =
typeof namespace === 'string'
? namespace
: namespace(root.source.input.file)

if (!computedNamespace) {
return
}

root.walkRules(rule => {
if (canNamespaceSelectors(rule)) {
return
}

rule.selectors = rule.selectors.map(selector =>
namespaceSelector(selector, computedNamespace),
)
})
}
}
}

module.exports.postcss = true
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
]
]
},
"main": "dist/plugin.js",
"main": "lib/plugin.js",
"directories": {
"test": "tests"
},
Expand Down Expand Up @@ -53,9 +53,10 @@
"eslint-plugin-prettier": "3.1.1",
"mocha": "6.2.2",
"nyc": "14.1.1",
"postcss": "^8.0.0",
"postcss-scss": "2.0.0"
},
"dependencies": {
"postcss": "^7.0.0"
"peerDependencies": {
"postcss": "^8.0.0"
}
}
4 changes: 4 additions & 0 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ describe('Basic functionality', () => {
it('does not apply if computed namespace is falsy', () => {
transformSnapshot('.foo {}', { namespace: file => !!file })
})

it('works with multiple namespaces', () => {
transformSnapshot('.selector {}', { namespace: '.foo, .bar' })
})
})

describe(':root', () => {
Expand Down
2 changes: 2 additions & 0 deletions tests/test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ exports[`Basic functionality works with a regexp which matches multiple selector
"
`;

exports[`Basic functionality works with multiple namespaces 1`] = `".foo .selector, .bar .selector {}"`;

exports[`SCSS @for does work with @for and nested @for rules 1`] = `
".my-component .pony {
@for $i from 1 through 3 {
Expand Down