From 0ac0d6bf3a5015207b3f547824067653910b746e Mon Sep 17 00:00:00 2001 From: fengzilong Date: Thu, 22 Nov 2018 15:01:24 +0800 Subject: [PATCH] feat: support deep selectors, closes #22 --- __tests__/__snapshots__/index.test.js.md | 107 +++++++++++++++++++++ __tests__/__snapshots__/index.test.js.snap | Bin 1617 -> 1797 bytes __tests__/fixtures/deep-selectors.rgl | 22 +++++ __tests__/index.test.js | 4 + lib/postcss-plugins/add-scoped-id.js | 47 --------- lib/postcss-plugins/scoped.js | 95 ++++++++++++++++++ lib/style-rewriter.js | 4 +- package.json | 4 +- yarn.lock | 42 +++++++- 9 files changed, 271 insertions(+), 54 deletions(-) create mode 100644 __tests__/fixtures/deep-selectors.rgl delete mode 100644 lib/postcss-plugins/add-scoped-id.js create mode 100644 lib/postcss-plugins/scoped.js diff --git a/__tests__/__snapshots__/index.test.js.md b/__tests__/__snapshots__/index.test.js.md index c5365df..38a94a4 100644 --- a/__tests__/__snapshots__/index.test.js.md +++ b/__tests__/__snapshots__/index.test.js.md @@ -161,6 +161,113 @@ Generated by [AVA](https://ava.li). `, ] +## deep-selectors + +> Snapshot 1 + + [ + `webpackJsonp([0],{␊ + ␊ + /***/ 18:␊ + /***/ (function(module, exports) {␊ + ␊ + module.exports = [{"type":"element","tag":"div","attrs":[{"type":"attribute","name":"class","value":"root"},{"type":"attribute","name":"data-r-2000be7d","value":""}],"children":[{"type":"text","text":"\\n deep selectors\\n"}]}]␊ + ␊ + /***/ }),␊ + ␊ + /***/ 19:␊ + /***/ (function(module, exports, __webpack_require__) {␊ + ␊ + "use strict";␊ + // ␊ + //␊ + // ␊ + //␊ + // ␊ + ␊ + ␊ + /***/ }),␊ + ␊ + /***/ 20:␊ + /***/ (function(module, exports) {␊ + ␊ + // removed by extract-text-webpack-plugin␊ + ␊ + /***/ }),␊ + ␊ + /***/ 22:␊ + /***/ (function(module, exports, __webpack_require__) {␊ + ␊ + var __regular_script__, __regular_template__;␊ + __webpack_require__(20)␊ + __regular_script__ = __webpack_require__(19)␊ + __regular_template__ = __webpack_require__(18)␊ + var Regular = __webpack_require__( 21 );␊ + ␊ + var __rs__ = __regular_script__ || {};␊ + if (__rs__.__esModule) __rs__ = __rs__["default"];␊ + if (Regular.__esModule) Regular = Regular["default"];␊ + ␊ + var __Component__, __cps__;␊ + if( typeof __rs__ === "object" ) {␊ + __rs__.template = __regular_template__;␊ + __Component__ = Regular.extend(__rs__);␊ + __cps__ = __rs__.components || __rs__.component;␊ + if( typeof __cps__ === "object" ) {␊ + for( var i in __cps__ ) {␊ + __Component__.component(i, __cps__[ i ]);␊ + }␊ + }␊ + } else if( typeof __rs__ === "function" && ( __rs__.prototype instanceof Regular ) ) {␊ + __rs__.prototype.template = __regular_template__;␊ + __Component__ = __rs__;␊ + }␊ + module.exports = __Component__;␊ + ␊ + /***/ })␊ + ␊ + },[22]);`, + `␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + .root[data-r-2000be7d] {␊ + color: red;␊ + }␊ + ␊ + .root .child[data-r-2000be7d] {␊ + color: yellow;␊ + }␊ + ␊ + .root[data-r-2000be7d] .child {␊ + color: blue;␊ + }␊ + `, + ] + ## multiple-css > Snapshot 1 diff --git a/__tests__/__snapshots__/index.test.js.snap b/__tests__/__snapshots__/index.test.js.snap index 6698bde02382f7c2b613257d67f9aeb735678ee4..a812e2e031287a50124446bdc688da00b6461b9a 100644 GIT binary patch literal 1797 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 PpVs{YYN#DDKq&wKHE9_~ diff --git a/__tests__/fixtures/deep-selectors.rgl b/__tests__/fixtures/deep-selectors.rgl new file mode 100644 index 0000000..2032760 --- /dev/null +++ b/__tests__/fixtures/deep-selectors.rgl @@ -0,0 +1,22 @@ + + + + + diff --git a/__tests__/index.test.js b/__tests__/index.test.js index cd0b8d3..7f88fca 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -27,3 +27,7 @@ test.serial( 'preserve-whitespace', async t => { test.serial( 'scoped-css', async t => { t.snapshot( await bundle( 'scoped-css.rgl' ) ) } ) + +test.serial( 'deep-selectors', async t => { + t.snapshot( await bundle( 'deep-selectors.rgl' ) ) +} ) diff --git a/lib/postcss-plugins/add-scoped-id.js b/lib/postcss-plugins/add-scoped-id.js deleted file mode 100644 index 1bb0d7a..0000000 --- a/lib/postcss-plugins/add-scoped-id.js +++ /dev/null @@ -1,47 +0,0 @@ -const postcss = require( 'postcss' ) -const selectorParser = require( 'postcss-selector-parser' ) - -module.exports = postcss.plugin( 'add-id', function ( opts ) { - return function ( root ) { - root.each( function rewriteSelector( node ) { - if ( !node.selector ) { - // handle media queries - if ( node.type === 'atrule' && node.name === 'media' ) { - node.each( rewriteSelector ) - } - return - } - node.selector = selectorParser( function ( selectors ) { - selectors.each( function ( selector ) { - let firstNode = null - let node = null - - selector.each( function ( n, i ) { - if ( n.type !== 'pseudo' ) { - if ( i === 0 ) { - firstNode = n - } - node = n - } - } ) - - selector.insertAfter( - firstNode, - selectorParser.attribute( { - attribute: opts.id - } ) - ) - - if ( firstNode !== node ) { - selector.insertAfter( - node, - selectorParser.attribute( { - attribute: opts.id - } ) - ) - } - } ) - } ).process( node.selector ).result - } ) - } -} ) diff --git a/lib/postcss-plugins/scoped.js b/lib/postcss-plugins/scoped.js new file mode 100644 index 0000000..2f892d0 --- /dev/null +++ b/lib/postcss-plugins/scoped.js @@ -0,0 +1,95 @@ +const postcss = require( 'postcss' ) +const selectorParser = require( 'postcss-selector-parser' ) + +module.exports = postcss.plugin( 'add-id', options => root => { + const id = options.id + const keyframes = Object.create( null ) + + root.each( function rewriteSelector( node ) { + if ( !node.selector ) { + // handle media queries + if ( node.type === 'atrule' ) { + if ( node.name === 'media' || node.name === 'supports' ) { + node.each( rewriteSelector ) + } else if ( /-?keyframes$/.test( node.name ) ) { + // register keyframes + keyframes[ node.params ] = node.params = node.params + '-' + id + } + } + return + } + node.selector = selectorParser( selectors => { + selectors.each( selector => { + let node = null + + selector.each( n => { + // ">>>" combinator + if ( n.type === 'combinator' && n.value === '>>>' ) { + n.value = ' ' + n.spaces.before = n.spaces.after = '' + return false + } + // /deep/ alias for >>>, since >>> doesn't work in SASS + if ( n.type === 'tag' && n.value === '/deep/' ) { + const prev = n.prev() + if ( prev && prev.type === 'combinator' && prev.value === ' ' ) { + prev.remove() + } + n.remove() + return false + } + if ( n.type !== 'pseudo' && n.type !== 'combinator' ) { + node = n + } + } ) + + if ( node ) { + node.spaces.after = '' + } else { + // For deep selectors & standalone pseudo selectors, + // the attribute selectors are prepended rather than appended. + // So all leading spaces must be eliminated to avoid problems. + selector.first.spaces.before = '' + } + + selector.insertAfter( + node, + selectorParser.attribute( { + attribute: id + } ) + ) + } ) + } ).processSync( node.selector ) + } ) + + // If keyframes are found in this