Skip to content

Commit 67e451e

Browse files
authored
feat: support deep selectors, closes #22 (#26)
1 parent c5c5178 commit 67e451e

File tree

9 files changed

+271
-54
lines changed

9 files changed

+271
-54
lines changed

Diff for: __tests__/__snapshots__/index.test.js.md

+107
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,113 @@ Generated by [AVA](https://ava.li).
161161
`,
162162
]
163163

164+
## deep-selectors
165+
166+
> Snapshot 1
167+
168+
[
169+
`webpackJsonp([0],{␊
170+
171+
/***/ 18:␊
172+
/***/ (function(module, exports) {␊
173+
174+
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"}]}]␊
175+
176+
/***/ }),␊
177+
178+
/***/ 19:␊
179+
/***/ (function(module, exports, __webpack_require__) {␊
180+
181+
"use strict";␊
182+
// <template>␊
183+
// <div class="root">␊
184+
// deep selectors␊
185+
// </div>␊
186+
// </template>␊
187+
//␊
188+
// <script>␊
189+
// </script>␊
190+
//␊
191+
// <style scoped>␊
192+
// .root {␊
193+
// color: red;␊
194+
// }␊
195+
//␊
196+
// .root .child {␊
197+
// color: yellow;␊
198+
// }␊
199+
//␊
200+
// .root >>> .child {␊
201+
// color: blue;␊
202+
// }␊
203+
// </style>␊
204+
205+
206+
/***/ }),␊
207+
208+
/***/ 20:␊
209+
/***/ (function(module, exports) {␊
210+
211+
// removed by extract-text-webpack-plugin␊
212+
213+
/***/ }),␊
214+
215+
/***/ 22:␊
216+
/***/ (function(module, exports, __webpack_require__) {␊
217+
218+
var __regular_script__, __regular_template__;␊
219+
__webpack_require__(20)␊
220+
__regular_script__ = __webpack_require__(19)␊
221+
__regular_template__ = __webpack_require__(18)␊
222+
var Regular = __webpack_require__( 21 );␊
223+
224+
var __rs__ = __regular_script__ || {};␊
225+
if (__rs__.__esModule) __rs__ = __rs__["default"];␊
226+
if (Regular.__esModule) Regular = Regular["default"];␊
227+
228+
var __Component__, __cps__;␊
229+
if( typeof __rs__ === "object" ) {␊
230+
__rs__.template = __regular_template__;␊
231+
__Component__ = Regular.extend(__rs__);␊
232+
__cps__ = __rs__.components || __rs__.component;␊
233+
if( typeof __cps__ === "object" ) {␊
234+
for( var i in __cps__ ) {␊
235+
__Component__.component(i, __cps__[ i ]);␊
236+
}␊
237+
}␊
238+
} else if( typeof __rs__ === "function" && ( __rs__.prototype instanceof Regular ) ) {␊
239+
__rs__.prototype.template = __regular_template__;␊
240+
__Component__ = __rs__;␊
241+
}␊
242+
module.exports = __Component__;␊
243+
244+
/***/ })␊
245+
246+
},[22]);`,
247+
`␊
248+
249+
250+
251+
252+
253+
254+
255+
256+
257+
.root[data-r-2000be7d] {␊
258+
color: red;␊
259+
}␊
260+
261+
.root .child[data-r-2000be7d] {␊
262+
color: yellow;␊
263+
}␊
264+
265+
.root[data-r-2000be7d] .child {␊
266+
color: blue;␊
267+
}␊
268+
`,
269+
]
270+
164271
## multiple-css
165272

166273
> Snapshot 1

Diff for: __tests__/__snapshots__/index.test.js.snap

180 Bytes
Binary file not shown.

Diff for: __tests__/fixtures/deep-selectors.rgl

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<template>
2+
<div class="root">
3+
deep selectors
4+
</div>
5+
</template>
6+
7+
<script>
8+
</script>
9+
10+
<style scoped>
11+
.root {
12+
color: red;
13+
}
14+
15+
.root .child {
16+
color: yellow;
17+
}
18+
19+
.root >>> .child {
20+
color: blue;
21+
}
22+
</style>

Diff for: __tests__/index.test.js

+4
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ test.serial( 'preserve-whitespace', async t => {
2727
test.serial( 'scoped-css', async t => {
2828
t.snapshot( await bundle( 'scoped-css.rgl' ) )
2929
} )
30+
31+
test.serial( 'deep-selectors', async t => {
32+
t.snapshot( await bundle( 'deep-selectors.rgl' ) )
33+
} )

Diff for: lib/postcss-plugins/add-scoped-id.js

-47
This file was deleted.

Diff for: lib/postcss-plugins/scoped.js

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
const postcss = require( 'postcss' )
2+
const selectorParser = require( 'postcss-selector-parser' )
3+
4+
module.exports = postcss.plugin( 'add-id', options => root => {
5+
const id = options.id
6+
const keyframes = Object.create( null )
7+
8+
root.each( function rewriteSelector( node ) {
9+
if ( !node.selector ) {
10+
// handle media queries
11+
if ( node.type === 'atrule' ) {
12+
if ( node.name === 'media' || node.name === 'supports' ) {
13+
node.each( rewriteSelector )
14+
} else if ( /-?keyframes$/.test( node.name ) ) {
15+
// register keyframes
16+
keyframes[ node.params ] = node.params = node.params + '-' + id
17+
}
18+
}
19+
return
20+
}
21+
node.selector = selectorParser( selectors => {
22+
selectors.each( selector => {
23+
let node = null
24+
25+
selector.each( n => {
26+
// ">>>" combinator
27+
if ( n.type === 'combinator' && n.value === '>>>' ) {
28+
n.value = ' '
29+
n.spaces.before = n.spaces.after = ''
30+
return false
31+
}
32+
// /deep/ alias for >>>, since >>> doesn't work in SASS
33+
if ( n.type === 'tag' && n.value === '/deep/' ) {
34+
const prev = n.prev()
35+
if ( prev && prev.type === 'combinator' && prev.value === ' ' ) {
36+
prev.remove()
37+
}
38+
n.remove()
39+
return false
40+
}
41+
if ( n.type !== 'pseudo' && n.type !== 'combinator' ) {
42+
node = n
43+
}
44+
} )
45+
46+
if ( node ) {
47+
node.spaces.after = ''
48+
} else {
49+
// For deep selectors & standalone pseudo selectors,
50+
// the attribute selectors are prepended rather than appended.
51+
// So all leading spaces must be eliminated to avoid problems.
52+
selector.first.spaces.before = ''
53+
}
54+
55+
selector.insertAfter(
56+
node,
57+
selectorParser.attribute( {
58+
attribute: id
59+
} )
60+
)
61+
} )
62+
} ).processSync( node.selector )
63+
} )
64+
65+
// If keyframes are found in this <style>, find and rewrite animation names
66+
// in declarations.
67+
// Caveat: this only works for keyframes and animation rules in the same
68+
// <style> element.
69+
if ( Object.keys( keyframes ).length ) {
70+
root.walkDecls( decl => {
71+
// individual animation-name declaration
72+
if ( /^(-\w+-)?animation-name$/.test( decl.prop ) ) {
73+
decl.value = decl.value
74+
.split( ',' )
75+
.map( v => keyframes[ v.trim() ] || v.trim() )
76+
.join( ',' )
77+
}
78+
// shorthand
79+
if ( /^(-\w+-)?animation$/.test( decl.prop ) ) {
80+
decl.value = decl.value
81+
.split( ',' )
82+
.map( v => {
83+
const vals = v.trim().split( /\s+/ )
84+
const i = vals.findIndex( val => keyframes[ val ] )
85+
if ( i !== -1 ) {
86+
vals.splice( i, 1, keyframes[ vals[ i ] ] )
87+
return vals.join( ' ' )
88+
}
89+
return v
90+
} )
91+
.join( ',' )
92+
}
93+
} )
94+
}
95+
} )

Diff for: lib/style-rewriter.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const postcss = require( 'postcss' )
22
const loaderUtils = require( 'loader-utils' )
3-
const addScopedId = require( './postcss-plugins/add-scoped-id' )
3+
const addScopedAttribute = require( './postcss-plugins/scoped' )
44
const loadOptions = require( './utils/options-cache' ).loadOptions
55
const loadPostcssConfig = require( './load-postcss-config' )
66

@@ -26,7 +26,7 @@ module.exports = function ( content, map ) {
2626

2727
// scoped css
2828
if ( query.scoped ) {
29-
plugins.push( addScopedId( { id: query.id } ) )
29+
plugins.push( addScopedAttribute( { id: query.id } ) )
3030
}
3131

3232
// sourcemap

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@
5252
"loader-utils": "^1.1.0",
5353
"lru-cache": "^4.0.1",
5454
"parse5": "^2.1.5",
55-
"postcss": "^5.0.21",
55+
"postcss": "^6.0.20",
5656
"postcss-load-config": "^1.2.0",
57-
"postcss-selector-parser": "^2.1.0",
57+
"postcss-selector-parser": "^3.1.1",
5858
"resolve": "^1.8.1",
5959
"source-map": "^0.5.6"
6060
},

0 commit comments

Comments
 (0)