Skip to content

Commit 93a57fa

Browse files
committed
Add CLI module
1 parent 6799b2a commit 93a57fa

File tree

13 files changed

+333
-0
lines changed

13 files changed

+333
-0
lines changed

cli/commands/compile.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const fs = require('fs');
2+
const postcss = require('postcss');
3+
const autoprefixer = require('autoprefixer');
4+
const nested = require('postcss-nested');
5+
const ifMedia = require('postcss-if-media');
6+
const scssSyntax = require('postcss-scss');
7+
const stripComments = require('postcss-strip-inline-comments');
8+
const mediaMinMax = require('postcss-media-minmax');
9+
const customMedia = require('postcss-custom-media');
10+
const imports = require('postcss-easy-import');
11+
const postcssFor = require('postcss-for');
12+
13+
const lh = require('./lib/lh');
14+
const typeScale = require('./lib/type-scale');
15+
16+
module.exports = file => {
17+
const ccss = fs.readFileSync(file, 'utf8');
18+
19+
return postcss()
20+
.use(imports())
21+
.use(postcssFor())
22+
.use(stripComments())
23+
.use(ifMedia())
24+
.use(nested())
25+
.use(customMedia())
26+
.use(mediaMinMax())
27+
.use(lh())
28+
.use(typeScale())
29+
.use(autoprefixer())
30+
.process(ccss, { parser: scssSyntax, from: file });
31+
}

cli/index.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env node
2+
3+
const cliparse = require('cliparse');
4+
const pkg = require('./package.json');
5+
const fs = require('fs');
6+
7+
const compile = require('./commands/compile');
8+
9+
cliparse.parse(cliparse.cli({
10+
name: 'concise-cli',
11+
description: 'Command-line Interface for Concise CSS',
12+
version: pkg.version,
13+
commands: [
14+
cliparse.command(
15+
'compile', {
16+
description: 'Compile code from Concise CSS',
17+
args: [
18+
cliparse.argument('input', { description: 'File to compile' }),
19+
cliparse.argument('output', { description: 'Output CSS file' })
20+
]
21+
}, (params) => {
22+
fs.writeFile(params.args[1], compile(params.args[0]), (err) => {
23+
if (err) {
24+
throw err;
25+
}
26+
27+
console.log(`File written: ${params.args[1]}\nFrom: ${params.args[0]}`);
28+
})
29+
}
30+
)
31+
]
32+
}));

cli/lib/lh.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const postcss = require('postcss')
2+
3+
const defaults = {
4+
rootSelector: ':root',
5+
unit: 'lh',
6+
lineHeight: 1.5
7+
}
8+
9+
module.exports = postcss.plugin('lh', (opts = defaults) => {
10+
const options = Object.assign(defaults, opts)
11+
12+
return css => {
13+
const lineHeight = getLineHeight(css, options)
14+
const lhReg = new RegExp('\\d*\\.?\\d+' + options.unit, 'gi')
15+
16+
css.replaceValues(lhReg, { fast: options.unit }, (val) => {
17+
return lhToRem(parseFloat(val), lineHeight)
18+
})
19+
}
20+
})
21+
22+
function getLineHeight (css, opts) {
23+
// Start with the default line-height
24+
let lineHeight = opts.lineHeight
25+
26+
// Walk over all the root selectors
27+
css.walkRules(opts.rootSelector, rule => {
28+
// Omit the process if the selector is inside a print media query
29+
if (rule.parent && rule.parent.params === 'print') return
30+
31+
// Walk over all the font or line-height properties
32+
rule.walkDecls(/font$|line-height/, decl => {
33+
// Matches {$1:font-size}{$2:unit}/{$3:line-height} when the property is 'font'
34+
const fontProps = decl.value.match(/(\d+|\d+?\.\d+)(r?em|px|%)(?:\s*\/\s*)(\d+|\d+?\.\d+)\s+/) || []
35+
36+
lineHeight = fontProps[3] || decl.value
37+
})
38+
})
39+
40+
return lineHeight
41+
}
42+
43+
function lhToRem(val, lineHeight) {
44+
return parseFloat((lineHeight * val).toFixed(3)) + 'rem'
45+
}

cli/lib/type-scale.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const postcss = require('postcss')
2+
3+
const defaults = {
4+
rootSelector: ':root',
5+
typeRatio: 1.2,
6+
ratioProperty: '--type-ratio'
7+
}
8+
9+
module.exports = postcss.plugin('type-scale', (opts = defaults) => {
10+
const options = Object.assign(defaults, opts)
11+
12+
return css => {
13+
const typeRatio = getTypeRatio(css, options)
14+
15+
css.walkDecls('font-size', decl => {
16+
// Replace only if it's a unitless value
17+
if (/\d+$/.test(decl.value)) decl.value = getSize(decl.value, typeRatio)
18+
})
19+
}
20+
})
21+
22+
function getTypeRatio (css, opts) {
23+
// Start with the default ratio
24+
let typeRatio = opts.typeRatio
25+
26+
// Walk over all the root selectors
27+
css.walkRules(opts.rootSelector, rule => {
28+
29+
// Omit the process if the selector is inside a print media query
30+
if (rule.parent && rule.parent.params === 'print') return
31+
32+
// Walk over all the font-size rules
33+
rule.walkDecls(opts.ratioProperty, decl => {
34+
typeRatio = parseFloat(decl.value)
35+
})
36+
})
37+
38+
return typeRatio
39+
}
40+
41+
function getSize(val, ratio) {
42+
return parseFloat(Math.pow(ratio, parseInt(val) - 2).toFixed(4)) + 'rem'
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@media (max-width: 768px) {
2+
.element {
3+
width: 20%;
4+
}
5+
}
6+
7+
@media (min-width: 768px) and (max-width: 992px) {
8+
.element {
9+
width: 30%;
10+
}
11+
}
12+
13+
@media (min-width: 992px) and (max-width: 1200px) {
14+
.element {
15+
width: 40%;
16+
}
17+
}
18+
19+
@media (min-width: 1200px) {
20+
.element {
21+
width: 50%;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@custom-media --only-extra-small (width <= 768px);
2+
@custom-media --only-small (768px <= width <= 992px);
3+
@custom-media --only-medium (992px <= width <= 1200px);
4+
@custom-media --only-large (width >= 1200px);
5+
6+
@media (--only-extra-small) {
7+
.element {
8+
width: 20%;
9+
}
10+
}
11+
12+
@media (--only-small) {
13+
.element {
14+
width: 30%;
15+
}
16+
}
17+
18+
@media (--only-medium) {
19+
.element {
20+
width: 40%;
21+
}
22+
}
23+
24+
@media (--only-large) {
25+
.element {
26+
width: 50%;
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
::-moz-range-track {
2+
background: #3071a9;
3+
width: 100%
4+
}
5+
6+
::-ms-track {
7+
background: #3071a9;
8+
width: 100%
9+
}
10+
11+
::-webkit-slider-runnable-track {
12+
background: #3071a9;
13+
width: 100%
14+
}
15+
16+
::-moz-range-thumb {
17+
border-radius: 3px;
18+
cursor: pointer
19+
}
20+
21+
::-ms-thumb {
22+
border-radius: 3px;
23+
cursor: pointer
24+
}
25+
26+
::-webkit-slider-thumb {
27+
border-radius: 3px;
28+
cursor: pointer
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
::range-track {
2+
background: #3071a9;
3+
width: 100%;
4+
}
5+
6+
::range-thumb {
7+
border-radius: 3px;
8+
cursor: pointer;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@media (max-width: 30em) {
2+
.element {
3+
width: 50%; } }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@custom-media --small-viewport (width <= 30em);
2+
3+
$width: 50%;
4+
5+
@media (--small-viewport) {
6+
.element {
7+
width: $width;
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
:root {
2+
font: 16px / 1.5 "Helvetica", "Arial", sans-serif;
3+
}
4+
5+
@media (max-width: 500px) {
6+
:root {
7+
font-size: 14px;
8+
}
9+
}
10+
11+
@media print {
12+
:root {
13+
font: 11pt / 1.3 "Georgia", "Times New Roman", "Times", serif;
14+
}
15+
}
16+
17+
p {
18+
margin-bottom: 1.5rem;
19+
padding-top: 0.75rem;
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
:root {
2+
font: 16px / 1.5 "Helvetica", "Arial", sans-serif;
3+
4+
@media (max-width: 500px) {
5+
font-size: 14px;
6+
}
7+
}
8+
9+
@media print {
10+
:root {
11+
font: 11pt / 1.3 "Georgia", "Times New Roman", "Times", serif;
12+
}
13+
}
14+
15+
p {
16+
margin-bottom: 1lh;
17+
padding-top: .5lh;
18+
}

cli/test/index.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
const fs = require("fs");
2+
const test = require("tape");
3+
const compile = require('../lib/compile');
4+
5+
const actual = (file) => {
6+
return compile(`test/fixtures/${file}/${file}.scss`).replace(/\s+/g, '');
7+
};
8+
9+
const expected = (file) => {
10+
return fs.readFileSync(`test/fixtures/${file}/${file}.css`, 'utf8').replace(/\s+/g, '');
11+
};
12+
13+
test('@media', (t) => {
14+
t.equal(
15+
actual('customMedia'),
16+
expected('customMedia'),
17+
'should transform custom media queries');
18+
19+
t.equal(
20+
actual('mediaMinMax'),
21+
expected('mediaMinMax'),
22+
'should transform ranges in media queries');
23+
24+
t.end();
25+
});
26+
27+
test('units', (t) => {
28+
t.equal(
29+
actual('verticalRhythm'),
30+
expected('verticalRhythm'),
31+
'should be added');
32+
33+
t.end();
34+
});
35+
36+
test('range input', (t) => {
37+
t.equal(
38+
actual('input-range'),
39+
expected('input-range'),
40+
'should be transformed');
41+
42+
t.end();
43+
});

0 commit comments

Comments
 (0)