Skip to content

Commit d809936

Browse files
committed
Rename @extend @COMPOSES and constrain the context where it can be used to better match its semantics.
1 parent 066f97e commit d809936

13 files changed

+250
-268
lines changed

README.md

+17-21
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ In `sheet` mode, `j2c` follows a [**'local by default'**](https://medium.com/see
1818
Like SASS, LESS and Stylus, `j2c` supports:
1919

2020
- mixins
21-
- `@extend`
2221
- nested selectors (in `sheet` mode)
22+
- `@composes`, and `@extends`-like mechanism
2323

2424
All standard CSS at-rules are available out of the box, most importantly:
2525

@@ -55,7 +55,7 @@ The [home page](http://j2c.py.gy) has a few interactive demos.
5555
- [For building a style sheet: `j2c.sheet(rules)`](#for-building-a-style-sheet-j2csheetrules)
5656
- [Combining multiple selectors](#combining-multiple-selectors)
5757
- [At-rules](#at-rules)
58-
- [Mixins and @extend](#mixins-and-extend)
58+
- [Mixins and @composes](#mixins-and-composes)
5959
- [CSS Hacks](#css-hacks)
6060
- [Inserting a stylesheet in a document](#inserting-the-stylesheet-in-the-document)
6161
- [Isomorphic app support](#isomorphic-app-support)
@@ -380,57 +380,53 @@ becomes
380380

381381
For `@keyframes` rules, a `@-webkit-keyframes` block is automatically created with auto-prefixed property names.
382382

383-
#### Mixins and `@extend`
383+
#### Mixins and `@coposes`
384384

385-
Mixins and `@extend` make `j2c` sheets composable. Both techniques can be combined.
385+
Mixins and `@composes` make `j2c` sheets composable. Both techniques can be combined.
386386

387387
##### Mixins and source objects composition
388388

389389
For mixins, arrays works the same way at the selector level as they do at the property/value one. You can therefore use the [method described in the "inline" section](#mixins) to create mixins, that can return either at-rules, selectors, properties or a mix thereof.
390390

391-
##### `@extend`
391+
##### `@composes`
392392

393-
`j2c` also supports a SASS-like `@extend`, more powerful in some regards, but more limited in others.
393+
`j2c` also supports `@composes`, which works a bit like the SASS`@extend`, more powerful in some regards, but more limited in others.
394394

395395
The limitation is that it can only deal with classes. Specifically:
396396

397397
```JS
398-
namespace = j2c.sheet({
399-
'.red': {color: '#f00'}
400-
})
401-
402-
sheet = j2c.sheet(namespace, {
398+
sheet = j2c.sheet({
399+
'.red': {
400+
color: '#f00'
401+
},
403402
'.great': {
404403
fontSize: '3em'
405404
},
406-
'.greatRed': {
407-
'@extend': ['.great', '.red'] // you can also pass a single class
405+
// `scarlet` here is the target of the composition, `great` and `red` are the sources.
406+
'.scarlet': {
407+
'@composes': ['.great', '.red'] // you can also pass a single class
408408
}
409409
})
410410
```
411411

412-
`sheet.greatRed` is now defined as `'great_j2c... red_j2c... greatRed_j2c...'` (class names truncated for readability).
412+
`sheet.scarlet` is now defined as `'great__j2c-xxx red__j2c-xxx scarlet__j2c-xxx'` (class names truncated for readability).
413413

414414
The extra power comes from the fact that you can inherit from arbitrary classes, not just j2c-defined ones:
415415

416416
```JS
417417
sheet = j2c.sheet(namespace, {
418418
'.myButton': {
419-
'@extend': ':global(.button)', // coming, say, form Bootstrap
419+
'@composes': ':global(.button)', // coming, say, form Bootstrap
420420
color: theme.highlight
421421
}
422422
})
423423
```
424424

425425
Here, `sheet.myButton` is `'button myButton_j2c...'`.
426426

427-
While `@extend` can import from arbitrary classes, it only imports into local ones.
428-
429-
`@extend` works fine with nested selectors. If there are more than one class in a selector, `@extend` applies to the last (right-most) one.
430-
431-
###### Invalid uses
427+
While the `@composes` sources can be arbitrary classes, the target must be a local one. It will not work in global context.
432428

433-
If the last or only selector is a `:global(.klass)`, in `@global` context, or in the absence of a class in the selector, `@extend` is turned into a `at-extend` property and inserted as-is in the sheet.
429+
`@composes` doesn't support nested selectors, and doesn't work in conditional at rules. Its target must lie at the first nesting level.
434430

435431
#### CSS Hacks
436432

dist/j2c.amd.js

+32-39
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,6 @@ define(function () { 'use strict';
146146
}
147147
}
148148

149-
var findClass = /()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g
150-
151149
/**
152150
* Hanldes at-rules
153151
*
@@ -157,7 +155,7 @@ define(function () { 'use strict';
157155
* @param {string[]} v - Either parameters for block-less rules or their block
158156
* for the others.
159157
* @param {string} prefix - the current selector or a prefix in case of nested rules
160-
* @param {string} rawPrefix - as above, but without localization transformations
158+
* @param {string} composes - as above, but without localization transformations
161159
* @param {string} vendors - a list of vendor prefixes
162160
* @Param {boolean} local - are we in @local or in @global scope?
163161
* @param {object} ns - helper functions to populate or create the @local namespace
@@ -166,8 +164,8 @@ define(function () { 'use strict';
166164
* @param {function} ns.l - @local helper
167165
*/
168166

169-
function at(k, v, buf, prefix, rawPrefix, vendors, local, ns){
170-
var kk, i
167+
function at(k, v, buf, prefix, composes, vendors, local, ns){
168+
var i, kk
171169
if (/^@(?:namespace|import|charset)$/.test(k)) {
172170
if(type.call(v) == ARRAY){
173171
for (kk = 0; kk < v.length; kk++) {
@@ -185,38 +183,35 @@ define(function () { 'use strict';
185183
// add a @-webkit-keyframes block too.
186184

187185
buf.a('@-webkit-', k.slice(1), ' {\n')
188-
sheet(v, buf, '', '', ['webkit'])
186+
sheet(v, buf, '', 1, ['webkit'])
189187
buf.c('}\n')
190188

191189
buf.a(k, ' {\n')
192-
sheet(v, buf, '', '', vendors, local, ns)
190+
sheet(v, buf, '', 1, vendors, local, ns)
193191
buf.c('}\n')
194192

195-
} else if (/^@extends?$/.test(k)) {
193+
} else if (/^@composes$/.test(k)) {
196194
if (!local) {
197-
buf.c('@-error-cannot-extend-in-global-context ', JSON.stringify(rawPrefix), ';\n')
195+
buf.a('@-error-at-composes-in-at-global;\n')
196+
return
197+
}
198+
if (!composes) {
199+
buf.a('@-error-at-composes-no-nesting;\n')
198200
return
199201
}
200-
rawPrefix = splitSelector(rawPrefix)
201-
for(i = 0; i < rawPrefix.length; i++) {
202-
/*eslint-disable no-cond-assign*/
203-
// pick the last class to be extended
204-
while (kk = findClass.exec(rawPrefix[i])) k = kk[4]
205-
/*eslint-enable no-cond-assign*/
202+
composes = splitSelector(composes)
203+
for(i = 0; i < composes.length; i++) {
204+
k = /^\s*\.(\w+)\s*$/.exec(composes[i])
206205
if (k == null) {
207206
// the last class is a :global(.one)
208-
buf.c('@-error-cannot-extend-in-global-context ', JSON.stringify(rawPrefix[i]), ';\n')
209-
continue
210-
} else if (/^@extends?$/.test(k)) {
211-
// no class in the selector, therefore `k` hasn't been overwritten.
212-
buf.c('@-error-no-class-to-extend-in ', JSON.stringify(rawPrefix[i]), ';\n')
207+
buf.a('@-error-at-composes-bad-target ', JSON.stringify(composes[i]), ';\n')
213208
continue
214209
}
215-
ns.e(
210+
ns.c(
216211
type.call(v) == ARRAY ? v.map(function (parent) {
217-
return parent.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, ns.l)
218-
}).join(' ') : v.replace(/()(?::global\(\s*(\.[-\w]+)\s*\)|()\.([-\w]+))/, ns.l),
219-
k
212+
return parent.replace(/()(?::?global\(\s*\.?([-\w]+)\s*\)|()\.([-\w]+))/, ns.l)
213+
}).join(' ') : v.replace(/()(?::?global\(\s*\.?([-\w]+)\s*\)|()\.([-\w]+))/, ns.l),
214+
k[1]
220215
)
221216
}
222217
} else if (/^@(?:font-face$|viewport$|page )/.test(k)) {
@@ -233,14 +228,14 @@ define(function () { 'use strict';
233228
}
234229

235230
} else if (/^@global$/.test(k)) {
236-
sheet(v, buf, prefix, rawPrefix, vendors, 0, ns)
231+
sheet(v, buf, prefix, 1, vendors, 0, ns)
237232

238233
} else if (/^@local$/.test(k)) {
239-
sheet(v, buf, prefix, rawPrefix, vendors, 1, ns)
234+
sheet(v, buf, prefix, 1, vendors, 1, ns)
240235

241236
} else if (/^@(?:media |supports |document )./.test(k)) {
242237
buf.a(k, ' {\n')
243-
sheet(v, buf, prefix, rawPrefix, vendors, local, ns)
238+
sheet(v, buf, prefix, 1, vendors, local, ns)
244239
buf.c('}\n')
245240

246241
} else {
@@ -254,22 +249,22 @@ define(function () { 'use strict';
254249
* @param {array|string|object} statements - a source object or sub-object.
255250
* @param {string[]} buf - the buffer in which the final style sheet is built
256251
* @param {string} prefix - the current selector or a prefix in case of nested rules
257-
* @param {string} rawPrefix - as above, but without localization transformations
252+
* @param {string} composes - the potential target of a @composes rule, if any.
258253
* @param {string} vendors - a list of vendor prefixes
259254
* @Param {boolean} local - are we in @local or in @global scope?
260255
* @param {object} ns - helper functions to populate or create the @local namespace
261-
* and to @extend classes
262-
* @param {function} ns.e - @extend helper
256+
* and to @composes classes
257+
* @param {function} ns.e - @composes helper
263258
* @param {function} ns.l - @local helper
264259
*/
265-
function sheet(statements, buf, prefix, rawPrefix, vendors, local, ns) {
266-
var k, kk, v, inDeclaration
260+
function sheet(statements, buf, prefix, composes, vendors, local, ns) {
261+
var k, v, inDeclaration
267262

268263
switch (type.call(statements)) {
269264

270265
case ARRAY:
271266
for (k = 0; k < statements.length; k++)
272-
sheet(statements[k], buf, prefix, rawPrefix, vendors, local, ns)
267+
sheet(statements[k], buf, prefix, composes, vendors, local, ns)
273268
break
274269

275270
case OBJECT:
@@ -285,15 +280,15 @@ define(function () { 'use strict';
285280
// Handle At-rules
286281
inDeclaration = (inDeclaration && buf.c('}\n') && 0)
287282

288-
at(k, v, buf, prefix, rawPrefix, vendors, local, ns)
283+
at(k, v, buf, prefix, composes, vendors, local, ns)
289284

290285
} else {
291286
// selector or nested sub-selectors
292287

293288
inDeclaration = (inDeclaration && buf.c('}\n') && 0)
294289

295290
sheet(v, buf,
296-
(kk = /,/.test(prefix) || prefix && /,/.test(k)) ?
291+
(/,/.test(prefix) || prefix && /,/.test(k)) ?
297292
cartesian(splitSelector(prefix), splitSelector( local ?
298293
k.replace(
299294
/()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g, ns.l
@@ -304,9 +299,7 @@ define(function () { 'use strict';
304299
/()(?::global\(\s*(\.[-\w]+)\s*\)|(\.)([-\w]+))/g, ns.l
305300
) : k
306301
), prefix),
307-
kk ?
308-
cartesian(splitSelector(rawPrefix), splitSelector(k), rawPrefix).join(',') :
309-
concat(rawPrefix, k, rawPrefix),
302+
composes || prefix ? '' : k,
310303
vendors,
311304
local, ns
312305
)
@@ -397,7 +390,7 @@ define(function () { 'use strict';
397390
}
398391

399392
var state = {
400-
e: function extend(parent, child) {
393+
c: function composes(parent, child) {
401394
var nameList = locals[child]
402395
locals[child] =
403396
nameList.slice(0, nameList.lastIndexOf(' ') + 1) +

dist/j2c.amd.min.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)