diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index acbf8a5..0000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-# https://www.appveyor.com/docs/appveyor-yml
-
-environment:
- matrix:
- - nodejs_version: 4
-
-version: "{build}"
-build: off
-deploy: off
-
-install:
- - ps: Install-Product node $env:nodejs_version
- - npm install --ignore-scripts
-
-test_script:
- - node --version
- - npm --version
- - cmd: "npm test"
diff --git a/.gitignore b/.gitignore
index de73e5a..688f9cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,11 @@
-node_modules
-index.*.js
-package-lock.json
-*.log*
-*.result.css
.*
-!.appveyor.yml
!.editorconfig
!.gitignore
!.rollup.js
!.tape.js
!.travis.yml
+*.log*
+*.result.css
+/index.*
+node_modules
+package-lock.json
diff --git a/.rollup.js b/.rollup.js
index 0436758..1a4c229 100644
--- a/.rollup.js
+++ b/.rollup.js
@@ -1,15 +1,15 @@
import babel from 'rollup-plugin-babel';
export default {
- input: 'index.js',
+ input: 'src/index.js',
output: [
- { file: 'index.cjs.js', format: 'cjs' },
- { file: 'index.es.js', format: 'es' }
+ { file: 'index.js', format: 'cjs', sourcemap: true, strict: false },
+ { file: 'index.mjs', format: 'esm', sourcemap: true, strict: false }
],
plugins: [
babel({
presets: [
- ['env', { modules: false, targets: { node: 4 } }]
+ ['@babel/env', { modules: false, targets: { node: 8 } }]
]
})
]
diff --git a/.tape.js b/.tape.js
index a4ef6a3..1ca6163 100644
--- a/.tape.js
+++ b/.tape.js
@@ -1,5 +1,8 @@
+const postcss = require('postcss');
+const postcssNesting = require('postcss-nesting');
+const postcssExtends = require('.');
+
module.exports = {
- 'postcss-extend-rule': {
'basic': {
message: 'supports @extend usage'
},
@@ -23,32 +26,47 @@ module.exports = {
},
'advanced': {
message: 'supports mixed usage (with postcss-nesting)',
- plugin: () => require('postcss')(
- require('postcss-nesting'),
- require('.')
- )
+ plugin: postcss.plugin('postcss-extend-rule', () => {
+ const extendsTransformer = postcssExtends();
+ const nestingTransformer = postcssNesting();
+
+ return (...args) => {
+ nestingTransformer(...args);
+ extendsTransformer(...args);
+ };
+ })
},
'nested-media': {
'message': 'supports nested @media usage'
},
'nested-media:nesting-first': {
'message': 'supports nested @media usage when postcss-nesting runs first',
- plugin: () => require('postcss')(
- require('postcss-nesting'),
- require('.')
- )
+ plugin: postcss.plugin('postcss-extend-rule', () => {
+ const extendsTransformer = postcssExtends();
+ const nestingTransformer = postcssNesting();
+
+ return (...args) => {
+ nestingTransformer(...args);
+ extendsTransformer(...args);
+ };
+ })
},
'nested-media:nesting-second': {
'message': 'supports nested @medi usage when postcss-nesting runs second',
- plugin: () => require('postcss')(
- require('.'),
- require('postcss-nesting')
- )
+ plugin: postcss.plugin('postcss-extend-rule', () => {
+ const extendsTransformer = postcssExtends();
+ const nestingTransformer = postcssNesting();
+
+ return (...args) => {
+ extendsTransformer(...args);
+ nestingTransformer(...args);
+ };
+ })
},
- 'errors': {
+ 'error': {
message: 'manages error-ridden usage'
},
- 'errors:ignore': {
+ 'error:ignore': {
message: 'manages error-ridden usage with { onFunctionalSelector: "ignore", onRecursiveExtend: "ignore", onUnusedExtend: "ignore" } options',
options: {
onFunctionalSelector: 'ignore',
@@ -56,16 +74,16 @@ module.exports = {
onUnusedExtend: 'ignore'
}
},
- 'errors:warn': {
+ 'error:warn': {
message: 'manages error-ridden usage with { onFunctionalSelector: "warn", onRecursiveExtend: "warn", onUnusedExtend: "warn" } options',
options: {
onFunctionalSelector: 'warn',
onRecursiveExtend: 'warn',
onUnusedExtend: 'warn'
},
- warning: 6
+ warnings: 2
},
- 'errors:throw': {
+ 'error:throw': {
message: 'manages error-ridden usage with { onFunctionalSelector: "throw", onRecursiveExtend: "throw", onUnusedExtend: "throw" } options',
options: {
onFunctionalSelector: 'throw',
@@ -76,7 +94,7 @@ module.exports = {
reason: 'Unused extend at-rule "some-non-existent-selector"'
}
},
- 'errors:throw-on-functional-selectors': {
+ 'error:throw-on-functional-selectors': {
message: 'manages error-ridden usage with { onFunctionalSelector: "throw" } options',
options: {
onFunctionalSelector: 'throw'
@@ -85,5 +103,4 @@ module.exports = {
reason: 'Encountered functional selector "%test-placeholder"'
}
}
- }
};
diff --git a/.travis.yml b/.travis.yml
index c564664..6ebed1d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@
language: node_js
node_js:
- - 4
+ - 8
install:
- npm install --ignore-scripts
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 00eda4b..7fd7a32 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,7 +26,7 @@ scope and avoid unrelated commits.
cd postcss-extend-rule
# Assign the original repo to a remote called "upstream"
- git remote add upstream git@github.com:jonathantneal/postcss-extend-rule.git
+ git remote add upstream git@github.com:csstools/postcss-extend-rule.git
# Install the tools necessary for testing
npm install
diff --git a/README.md b/README.md
index 3b1be1c..e836297 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# PostCSS Extend Rule [
][postcss]
+# PostCSS Extend Rule [
][postcss]
[![NPM Version][npm-img]][npm-url]
[![Build Status][cli-img]][cli-url]
@@ -57,126 +57,35 @@
## Usage
-Add [PostCSS Extend Rule] to your build tool:
+Add [PostCSS Extend Rule] to your project:
```bash
npm install postcss-extend-rule --save-dev
```
-#### Node
-
-Use [PostCSS Extend Rule] to process your CSS:
+Use **PostCSS Extend Rule** to process your CSS:
```js
-import postcssExtend from 'postcss-extend-rule';
+const postcssExtendRule = require('postcss-extend-rule');
-postcssExtend.process(YOUR_CSS, /* processOptions */ /*, pluginOptions */);
+postcssExtendRule.process(YOUR_CSS /*, processOptions, pluginOptions */);
```
-#### PostCSS
-
-Add [PostCSS] to your build tool:
-
-```bash
-npm install postcss --save-dev
-```
-
-Use [PostCSS Extend Rule] as a plugin:
+Or use it as a [PostCSS] plugin:
```js
-import postcss from 'gulp-postcss';
-import postcssExtend from 'postcss-extend-rule';
+const postcss = require('postcss');
+const postcssExtendRule = require('postcss-extend-rule');
postcss([
- postcssExtend(/* pluginOptions */)
-]).process(YOUR_CSS);
-```
-
-#### Webpack
-
-Add [PostCSS Loader] to your build tool:
-
-```bash
-npm install postcss-loader --save-dev
-```
-
-Use [PostCSS Extend Rule] in your Webpack configuration:
-
-```js
-import postcssExtend from 'postcss-extend-rule';
-
-module.exports = {
- module: {
- rules: [
- {
- test: /\.css$/,
- use: [
- 'style-loader',
- { loader: 'css-loader', options: { importLoaders: 1 } },
- { loader: 'postcss-loader', options: {
- ident: 'postcss',
- plugins: () => [
- postcssExtend(/* pluginOptions */)
- ]
- } }
- ]
- }
- ]
- }
-}
+ postcssExtendRule(/* pluginOptions */)
+]).process(YOUR_CSS /*, processOptions */);
```
-#### Gulp
+**PostCSS Extend Rule** runs in all Node environments, with special instructions for:
-Add [Gulp PostCSS] to your build tool:
-
-```bash
-npm install gulp-postcss --save-dev
-```
-
-Use [PostCSS Extend Rule] in your Gulpfile:
-
-```js
-import postcss from 'gulp-postcss';
-import postcssExtend from 'postcss-extend-rule';
-
-gulp.task('css', () => gulp.src('./src/*.css').pipe(
- postcss([
- postcssExtend(/* pluginOptions */)
- ])
-).pipe(
- gulp.dest('.')
-));
-```
-
-#### Grunt
-
-Add [Grunt PostCSS] to your build tool:
-
-```bash
-npm install grunt-postcss --save-dev
-```
-
-Use [PostCSS Extend Rule] in your Gruntfile:
-
-```js
-import postcssExtend from 'postcss-extend-rule';
-
-grunt.loadNpmTasks('grunt-postcss');
-
-grunt.initConfig({
- postcss: {
- options: {
- use: [
- postcssExtend(/* pluginOptions */)
- ]
- },
- dist: {
- src: '*.css'
- }
- }
-});
-```
+| [Node](INSTALL.md#node) | [PostCSS CLI](INSTALL.md#postcss-cli) | [Webpack](INSTALL.md#webpack) | [Create React App](INSTALL.md#create-react-app) | [Gulp](INSTALL.md#gulp) | [Grunt](INSTALL.md#grunt) |
+| --- | --- | --- | --- | --- | --- |
## Options
@@ -256,8 +165,8 @@ main {
}
```
-[cli-img]: https://img.shields.io/travis/jonathantneal/postcss-extend-rule.svg
-[cli-url]: https://travis-ci.org/jonathantneal/postcss-extend-rule
+[cli-img]: https://img.shields.io/travis/csstools/postcss-extend-rule/master.svg
+[cli-url]: https://travis-ci.org/csstools/postcss-extend-rule
[git-img]: https://img.shields.io/badge/support-chat-blue.svg
[git-url]: https://gitter.im/postcss/postcss
[npm-img]: https://img.shields.io/npm/v/postcss-extend-rule.svg
@@ -265,8 +174,5 @@ main {
[CSS Extend Rules Specification]: https://jonathantneal.github.io/specs/css-extend-rule/
[Functional Selectors]: https://jonathantneal.github.io/specs/css-extend-rule/#functional-selector
-[Gulp PostCSS]: https://github.com/postcss/gulp-postcss
-[Grunt PostCSS]: https://github.com/nDmitry/grunt-postcss
[PostCSS]: https://github.com/postcss/postcss
-[PostCSS Loader]: https://github.com/postcss/postcss-loader
-[PostCSS Extend Rule]: https://github.com/jonathantneal/postcss-extend-rule
+[PostCSS Extend Rule]: https://github.com/csstools/postcss-extend-rule
diff --git a/package.json b/package.json
index 3ea5c2a..1fa5a30 100644
--- a/package.json
+++ b/package.json
@@ -1,46 +1,59 @@
{
"name": "postcss-extend-rule",
- "version": "2.0.0",
+ "version": "3.0.0",
"description": "Use the @extend at-rule and functional selectors in CSS",
"author": "Jonathan Neal ",
"license": "CC0-1.0",
- "repository": "jonathantneal/postcss-extend-rule",
- "homepage": "https://github.com/jonathantneal/postcss-extend-rule#readme",
- "bugs": "https://github.com/jonathantneal/postcss-extend-rule/issues",
- "main": "index.cjs.js",
- "module": "index.es.js",
+ "repository": "csstools/postcss-extend-rule",
+ "homepage": "https://github.com/csstools/postcss-extend-rule#readme",
+ "bugs": "https://github.com/csstools/postcss-extend-rule/issues",
+ "main": "index.js",
+ "module": "index.mjs",
"files": [
- "index.cjs.js",
- "index.es.js"
+ "index.js",
+ "index.js.map",
+ "index.mjs",
+ "index.mjs.map"
],
"scripts": {
+ "build": "rollup --config .rollup.js --silent",
"prepublishOnly": "npm test",
- "pretest": "rollup -c .rollup.js --silent",
- "test": "echo 'Running tests...'; npm run test:js && npm run test:tape",
- "test:js": "eslint *.js --cache --ignore-path .gitignore --quiet",
+ "pretest:tape": "npm run build",
+ "test": "npm run test:js && npm run test:tape",
+ "test:js": "eslint src/{*,**/*}.js --cache --ignore-path .gitignore --quiet",
"test:tape": "postcss-tape"
},
"engines": {
- "node": ">=4.0.0"
+ "node": ">=8.0.0"
},
"dependencies": {
- "postcss": "^6.0.22",
- "postcss-nesting": "^5.0.0"
+ "postcss": "^7.0.17",
+ "postcss-nesting": "^7.0.1",
+ "postcss-tape": "^5.0.2"
},
"devDependencies": {
- "babel-core": "^6.26.3",
- "babel-eslint": "^8.2.3",
- "babel-preset-env": "^1.7.0",
- "eslint": "^4.19.1",
- "eslint-config-dev": "^2.0.0",
- "postcss-tape": "^2.2.0",
+ "@babel/core": "^7.5.5",
+ "@babel/preset-env": "^7.5.5",
+ "babel-eslint": "^10.0.2",
+ "eslint": "^6.1.0",
"pre-commit": "^1.2.2",
- "rollup": "^0.59.4",
- "rollup-plugin-babel": "^3.0.4"
+ "rollup": "^1.17.0",
+ "rollup-plugin-babel": "^4.3.3"
},
"eslintConfig": {
- "extends": "dev",
- "parser": "babel-eslint"
+ "env": {
+ "browser": true,
+ "es6": true,
+ "node": true
+ },
+ "extends": "eslint:recommended",
+ "parser": "babel-eslint",
+ "parserOptions": {
+ "ecmaVersion": 2018,
+ "impliedStrict": true,
+ "sourceType": "module"
+ },
+ "root": true
},
"keywords": [
"postcss",
diff --git a/index.js b/src/index.js
similarity index 88%
rename from index.js
rename to src/index.js
index f43a3d5..91139a3 100644
--- a/index.js
+++ b/src/index.js
@@ -1,5 +1,7 @@
import postcss from 'postcss';
-import nesting from 'postcss-nesting';
+import postcssNesting from 'postcss-nesting';
+
+const nesting = postcssNesting();
// functional selector match
const functionalSelectorMatch = /(^|[^\w-])(%[_a-zA-Z]+[_a-zA-Z0-9-]*)([^\w-]|$)/i;
@@ -15,11 +17,19 @@ export default postcss.plugin('postcss-extend-rule', rawopts => {
: 'extend';
return (root, result) => {
+ const extendedAtRules = new WeakMap();
+
// for each extend at-rule
root.walkAtRules(extendMatch, extendAtRule => {
+ let parent = extendAtRule.parent;
+
+ while (parent.parent && parent.parent !== root) {
+ parent = parent.parent;
+ }
+
// do not revisit visited extend at-rules
- if (!extendAtRule.__extendAtRuleVisited) {
- extendAtRule.__extendAtRuleVisited = true;
+ if (!extendedAtRules.has(extendAtRule)) {
+ extendedAtRules.set(extendAtRule, true);
// selector identifier
const selectorIdMatch = getSelectorIdMatch(extendAtRule.params);
@@ -33,13 +43,11 @@ export default postcss.plugin('postcss-extend-rule', rawopts => {
extendAtRule.replaceWith(extendingRules);
// transform any nesting at-rules
- extendingRules.forEach(
- extendingRule => {
- transform(extendingRule);
+ const cloneRoot = postcss.root().append(parent.clone());
- extendingRule.walk(transform);
- }
- );
+ nesting(cloneRoot);
+
+ parent.replaceWith(cloneRoot);
} else {
// manage unused extend at-rules
const unusedExtendMessage = `Unused extend at-rule "${extendAtRule.params}"`;
@@ -81,14 +89,6 @@ export default postcss.plugin('postcss-extend-rule', rawopts => {
};
});
-function transform(node) {
- return nesting()({
- walk(transformer) {
- return transformer(node)
- }
- });
-}
-
function getExtendingRules(selectorIdMatch, extendAtRule) {
// extending rules
const extendingRules = [];
@@ -138,7 +138,7 @@ function getSelectorIdMatch(selectorIds) {
).join('|');
// selector unattached to an existing selector
- const selectorIdMatch = new RegExp(`(^|[^\\w-]!\.!\#)(${escapedSelectorIds})([^\\w-]|$)`, '');
+ const selectorIdMatch = new RegExp(`(^|[^\\w-]!.!#)(${escapedSelectorIds})([^\\w-]|$)`, '');
return selectorIdMatch;
}
diff --git a/test/advanced.expect.css b/test/advanced.expect.css
index db86fca..038cdea 100644
--- a/test/advanced.expect.css
+++ b/test/advanced.expect.css
@@ -1,20 +1,24 @@
.serious-modal {
font-style: normal;
- font-weight: bold;
+ font-weight: bold
}
@media (max-width: 240px) {
- .serious-modal:not(:focus) {
- outline: none;
+.serious-modal:not(:focus) {
+ outline: none
+}
}
+
+.modal {
+ border: thick dotted red
}
.modal {
- border: thick dotted red;
- color: red;
+
+ color: red
}
.modal:hover:not(:focus) {
- outline: none;
-}
+ outline: none;
+ }
diff --git a/test/basic.button.expect.css b/test/basic.button.expect.css
index 60991f0..0042fb6 100644
--- a/test/basic.button.expect.css
+++ b/test/basic.button.expect.css
@@ -4,10 +4,16 @@ button {
.button {
color: red;
+}
+
+.button {
background: blue;
}
#button {
color: red;
+}
+
+#button {
background: lime;
}
diff --git a/test/basic.expect.css b/test/basic.expect.css
index f85069a..73a23cd 100644
--- a/test/basic.expect.css
+++ b/test/basic.expect.css
@@ -17,5 +17,6 @@
}
.serious-modal {
+
font-weight: bold;
}
diff --git a/test/errors.css b/test/error.css
similarity index 100%
rename from test/errors.css
rename to test/error.css
diff --git a/test/errors.expect.css b/test/error.expect.css
similarity index 55%
rename from test/errors.expect.css
rename to test/error.expect.css
index 86b6157..751a6c5 100644
--- a/test/errors.expect.css
+++ b/test/error.expect.css
@@ -2,10 +2,13 @@ test-does-not-extend-non-existent-selector {
}
test-does-not-extend-itself {
+ @extend test-does-not-extend-itself;
}
test-does-not-extend-itself-cleverly-1 {
+ @extend test-does-not-extend-itself-cleverly-1;
}
test-does-not-extend-itself-cleverly-2 {
+ @extend test-does-not-extend-itself-cleverly-1;
}
diff --git a/test/errors.ignore.expect.css b/test/error.ignore.expect.css
similarity index 58%
rename from test/errors.ignore.expect.css
rename to test/error.ignore.expect.css
index c137b53..9cfd9f9 100644
--- a/test/errors.ignore.expect.css
+++ b/test/error.ignore.expect.css
@@ -3,21 +3,17 @@ test-does-not-extend-non-existent-selector {
}
test-does-not-extend-itself {
-
- @extend test-does-not-extend-itself
+ @extend test-does-not-extend-itself;
}
test-does-not-extend-itself-cleverly-1 {
-
- @extend test-does-not-extend-itself-cleverly-1
+ @extend test-does-not-extend-itself-cleverly-1;
}
test-does-not-extend-itself-cleverly-2 {
-
- @extend test-does-not-extend-itself-cleverly-1
+ @extend test-does-not-extend-itself-cleverly-1;
}
%test-placeholder {
-
- @extend %test-placeholder
+ @extend %test-placeholder;
}
diff --git a/test/errors.warn.expect.css b/test/error.warn.expect.css
similarity index 58%
rename from test/errors.warn.expect.css
rename to test/error.warn.expect.css
index c137b53..9cfd9f9 100644
--- a/test/errors.warn.expect.css
+++ b/test/error.warn.expect.css
@@ -3,21 +3,17 @@ test-does-not-extend-non-existent-selector {
}
test-does-not-extend-itself {
-
- @extend test-does-not-extend-itself
+ @extend test-does-not-extend-itself;
}
test-does-not-extend-itself-cleverly-1 {
-
- @extend test-does-not-extend-itself-cleverly-1
+ @extend test-does-not-extend-itself-cleverly-1;
}
test-does-not-extend-itself-cleverly-2 {
-
- @extend test-does-not-extend-itself-cleverly-1
+ @extend test-does-not-extend-itself-cleverly-1;
}
%test-placeholder {
-
- @extend %test-placeholder
+ @extend %test-placeholder;
}
diff --git a/test/nested-media.expect.css b/test/nested-media.expect.css
index 4ddf203..7096b05 100644
--- a/test/nested-media.expect.css
+++ b/test/nested-media.expect.css
@@ -8,8 +8,11 @@
.test-content {
color: red;
+}
- @media screen and (min-width: 920px) {
+@media screen and (min-width: 920px) {
+
+ .test-content {
color: green;
- }
}
+ }
diff --git a/test/nested-media.nesting-first.expect.css b/test/nested-media.nesting-first.expect.css
index bb6f336..a82c9b2 100644
--- a/test/nested-media.nesting-first.expect.css
+++ b/test/nested-media.nesting-first.expect.css
@@ -1,13 +1,13 @@
.my_placeholder {
- color: red;
+ color: red
}
@media screen and (min-width: 920px) {
- .my_placeholder {
- color: green;
- }
+.my_placeholder {
+ color: green
}
+ }
.test-content {
color: red;
@@ -17,5 +17,5 @@
.test-content {
color: green;
- }
}
+ }
diff --git a/test/nested-media.nesting-second.expect.css b/test/nested-media.nesting-second.expect.css
index bb6f336..a82c9b2 100644
--- a/test/nested-media.nesting-second.expect.css
+++ b/test/nested-media.nesting-second.expect.css
@@ -1,13 +1,13 @@
.my_placeholder {
- color: red;
+ color: red
}
@media screen and (min-width: 920px) {
- .my_placeholder {
- color: green;
- }
+.my_placeholder {
+ color: green
}
+ }
.test-content {
color: red;
@@ -17,5 +17,5 @@
.test-content {
color: green;
- }
}
+ }