pax_global_header00006660000000000000000000000064127144420640014516gustar00rootroot0000000000000052 comment=a33fd5d40fa2e734e55ad40e2cdd0272e241f5b5 postcss-minify-font-values-1.0.5/000077500000000000000000000000001271444206400167515ustar00rootroot00000000000000postcss-minify-font-values-1.0.5/.editorconfig000066400000000000000000000002531271444206400214260ustar00rootroot00000000000000root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true indent_style = space indent_size = 2 [*.js] indent_size = 4 postcss-minify-font-values-1.0.5/.eslintrc000066400000000000000000000003061271444206400205740ustar00rootroot00000000000000root: true rules: semi: [2, 'always'] semi-spacing: [2] quotes: [2, 'single'] curly: [2, 'all'] brace-style: [2, '1tbs', { allowSingleLine: false }] space-after-keywords: [2, 'always'] postcss-minify-font-values-1.0.5/.gitignore000066400000000000000000000000151271444206400207350ustar00rootroot00000000000000node_modules postcss-minify-font-values-1.0.5/.travis.yml000066400000000000000000000000661271444206400210640ustar00rootroot00000000000000language: node_js node_js: - '5' - '4' - '0.12' postcss-minify-font-values-1.0.5/LICENSE000066400000000000000000000020621271444206400177560ustar00rootroot00000000000000Copyright (c) Bogdan Chadkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. postcss-minify-font-values-1.0.5/README.md000066400000000000000000000033241271444206400202320ustar00rootroot00000000000000# postcss-minify-font-values [![Build Status][ci-img]][ci] > Minify font declarations with PostCSS. This module will try to minimise the `font-family`, `font-weight` and `font` shorthand properties; it can unquote font families where necessary, detect & remove duplicates, and cut short a declaration after it finds a keyword. For more examples, see the [tests](test). ```css h1 { font:bold 2.2rem/.9 "Open Sans Condensed", sans-serif; } p { font-family: "Helvetica Neue", Arial, sans-serif, Helvetica; font-weight: normal; } ``` ```css h1 { font:700 2.2rem/.9 Open Sans Condensed,sans-serif } p { font-family: Helvetica Neue,Arial,sans-serif; font-weight: 400; } ``` ## API ### minifyFontValues([options]) #### options ##### removeAfterKeyword Type: `boolean` Default: `true` Pass `false` to disable the module from removing font families after it encounters a font keyword, for example `sans-serif`. ##### removeDuplicates Type: `boolean` Default: `true` Pass `false` to disable the module from removing duplicated font families. ##### removeQuotes Type: `boolean` Default: `true` Pass `false` to disable the module from removing quotes from font families. Note that oftentimes, this is a *safe optimisation* & is done safely. For more details, see [Mathias Bynens' article][mathias]. ## Usage ```js postcss([ require('postcss-minify-font-values') ]) ``` See [PostCSS] docs for examples for your environment. MIT © [Bogdan Chadkin](mailto:trysound@yandex.ru) [mathias]: https://mathiasbynens.be/notes/unquoted-font-family [PostCSS]: https://github.com/postcss/postcss [ci-img]: https://travis-ci.org/TrySound/postcss-minify-font-values.svg [ci]: https://travis-ci.org/TrySound/postcss-minify-font-values postcss-minify-font-values-1.0.5/index.js000066400000000000000000000023021271444206400204130ustar00rootroot00000000000000var assign = require('object-assign'); var postcss = require('postcss'); var valueParser = require('postcss-value-parser'); var minifyWeight = require('./lib/minify-weight'); var minifyFamily = require('./lib/minify-family'); var minifyFont = require('./lib/minify-font'); function transform(opts) { opts = assign({ removeAfterKeyword: true, removeDuplicates: true, removeQuotes: true }, opts); return function (decl) { var tree; if (decl.type === 'decl') { if (decl.prop === 'font-weight') { decl.value = minifyWeight(decl.value, opts); } else if (decl.prop === 'font-family') { tree = valueParser(decl.value); tree.nodes = minifyFamily(tree.nodes, opts); decl.value = tree.toString(); } else if (decl.prop === 'font') { tree = valueParser(decl.value); tree.nodes = minifyFont(tree.nodes, opts); decl.value = tree.toString(); } } }; } module.exports = postcss.plugin('postcss-minify-font-values', function (opts) { return function (css) { css.walk(transform(opts)); }; }); postcss-minify-font-values-1.0.5/lib/000077500000000000000000000000001271444206400175175ustar00rootroot00000000000000postcss-minify-font-values-1.0.5/lib/keywords.js000066400000000000000000000013531271444206400217260ustar00rootroot00000000000000var keywords = module.exports = { style: [ 'italic', 'oblique' ], variant: [ 'small-caps' ], weight: [ '100', '200', '300', '400', '500', '600', '700', '800', '900', 'bold', 'lighter', 'bolder' ], stretch: [ 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded' ], size: [ 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'larger', 'smaller' ] }; postcss-minify-font-values-1.0.5/lib/minify-family.js000066400000000000000000000051441271444206400226330ustar00rootroot00000000000000var stringify = require('postcss-value-parser').stringify; var uniqs = require('./uniqs')('monospace'); // Note that monospace is missing intentionally from this list; we should not // remove instances of duplicated monospace keywords, it causes the font to be // rendered smaller in Chrome. var keywords = [ 'sans-serif', 'serif', 'fantasy', 'cursive' ]; function intersection(haystack, array) { return array.some(function (v) { return ~haystack.indexOf(v); }); }; module.exports = function (nodes, opts) { var family = []; var last = null; var i, max; nodes.forEach(function (node, index, nodes) { var value = node.value; if (node.type === 'string' || node.type === 'function') { family.push(node); } else if (node.type === 'word') { if (!last) { last = { type: 'word', value: '' }; family.push(last); } last.value += node.value; } else if (node.type === 'space') { if (last && index !== nodes.length - 1) { last.value += ' '; } } else { last = null; } }); family = family.map(function (node) { if (node.type === 'string') { if ( !opts.removeQuotes || intersection(node.value, keywords) || /[0-9]/.test(node.value.slice(0, 1)) ) { return stringify(node); } var escaped = node.value.split(/\s/).map(function (word, index, words) { var next = words[index + 1]; if (next && /^[^a-z]/i.test(next)) { return word + '\\'; } if (!/^[^a-z\d\xa0-\uffff_-]/i.test(word)) { return word.replace(/([^a-z\d\xa0-\uffff_-])/gi, '\\$1'); } if (/^[^a-z]/i.test(word) && index < 1) { return '\\' + word; } return word; }).join(' '); if (escaped.length < node.value.length + 2) { return escaped; } } return stringify(node); }); if (opts.removeAfterKeyword) { for (i = 0, max = family.length; i < max; i += 1) { if (~keywords.indexOf(family[i])) { family = family.slice(0, i + 1); break; } } } if (opts.removeDuplicates) { family = uniqs(family); } return [ { type: 'word', value: family.join() } ]; }; postcss-minify-font-values-1.0.5/lib/minify-font.js000066400000000000000000000031611271444206400223150ustar00rootroot00000000000000var unit = require('postcss-value-parser').unit; var keywords = require('./keywords'); var minifyFamily = require('./minify-family'); var minifyWeight = require('./minify-weight'); module.exports = function (nodes, opts) { var i, max, node, familyStart, family; var hasSize = false; for (i = 0, max = nodes.length; i < max; i += 1) { node = nodes[i]; if (node.type === 'word') { if (node.value === 'normal' || ~keywords.style.indexOf(node.value) || ~keywords.variant.indexOf(node.value) || ~keywords.stretch.indexOf(node.value)) { if (!hasSize) { familyStart = i; } } else if (~keywords.weight.indexOf(node.value)) { if (!hasSize) { node.value = minifyWeight(node.value, opts); familyStart = i; } } else if (~keywords.size.indexOf(node.value) || unit(node.value)) { if (!hasSize) { familyStart = i; hasSize = true; } } } else if (node.type === 'div') { node.before = ''; node.after = ''; if (node.value === '/') { familyStart = i + 1; } break; } else if (node.type === 'space') { node.value = ' '; } } if (!isNaN(familyStart)) { familyStart += 2; family = minifyFamily(nodes.slice(familyStart), opts); nodes = nodes.slice(0, familyStart).concat(family); } return nodes; }; postcss-minify-font-values-1.0.5/lib/minify-weight.js000066400000000000000000000001611271444206400226330ustar00rootroot00000000000000module.exports = function (value) { return value === 'normal' ? '400' : value === 'bold' ? '700' : value; }; postcss-minify-font-values-1.0.5/lib/uniqs.js000066400000000000000000000005251271444206400212160ustar00rootroot00000000000000module.exports = function uniqueExcept (exclude) { return function unique () { var list = Array.prototype.concat.apply([], arguments); return list.filter(function (item, i) { if (item === exclude) { return true; } return i === list.indexOf(item); }); }; }; postcss-minify-font-values-1.0.5/package.json000066400000000000000000000017261271444206400212450ustar00rootroot00000000000000{ "name": "postcss-minify-font-values", "version": "1.0.5", "description": "Minify font declarations with PostCSS", "main": "index.js", "files": [ "index.js", "lib" ], "scripts": { "test": "tape test/*.js | tap-spec", "posttest": "eslint index.js lib test" }, "author": "Bogdan Chadkin ", "license": "MIT", "keywords": [ "css", "font", "font-family", "font-weight", "optimise", "postcss-plugin" ], "devDependencies": { "eslint": "^1.3.1", "tap-spec": "^4.1.0", "tape": "^4.2.0" }, "dependencies": { "object-assign": "^4.0.1", "postcss": "^5.0.4", "postcss-value-parser": "^3.0.2" }, "repository": { "type": "git", "url": "git+https://github.com/TrySound/postcss-minify-font-values.git" }, "bugs": { "url": "https://github.com/TrySound/postcss-minify-font-values/issues" }, "homepage": "https://github.com/TrySound/postcss-minify-font-values" } postcss-minify-font-values-1.0.5/test/000077500000000000000000000000001271444206400177305ustar00rootroot00000000000000postcss-minify-font-values-1.0.5/test/index.js000066400000000000000000000137121271444206400214010ustar00rootroot00000000000000var test = require('tape'); var postcss = require('postcss'); var plugin = require('../'); var name = require('../package.json').name; var tests = [{ message: 'should not unquote font names with a leading number', fixture: 'h1{font-family:"11880-icons"!important;}', expected: 'h1{font-family:"11880-icons"!important;}' }, { message: 'should unquote font names', fixture: 'h1{font-family:"Helvetica Neue"}', expected: 'h1{font-family:Helvetica Neue}' }, { message: 'should unquote and join identifiers with a slash, if numeric', fixture: 'h1{font-family:"Bond 007"}', expected: 'h1{font-family:Bond\\ 007}' }, { message: 'should not unquote if it would produce a bigger identifier', fixture: 'h1{font-family:"Call 0118 999 881 999 119 725 3"}', expected: 'h1{font-family:"Call 0118 999 881 999 119 725 3"}' }, { message: 'should not unquote font names if they contain keywords', fixture: 'h1{font-family:"slab serif"}', expected: 'h1{font-family:"slab serif"}' }, { message: 'should minimise space inside a legal font name', fixture: 'h1{font-family:Lucida Grande}', expected: 'h1{font-family:Lucida Grande}' }, { message: 'should minimise space around a list of font names', fixture: 'h1{font-family:Arial, Helvetica, sans-serif}', expected: 'h1{font-family:Arial,Helvetica,sans-serif}' }, { message: 'should dedupe font family names', fixture: 'h1{font-family:Helvetica,Arial,Helvetica,sans-serif}', expected: 'h1{font-family:Helvetica,Arial,sans-serif}' }, { message: 'should discard the rest of the declaration after a keyword', fixture: 'h1{font-family:Arial,sans-serif,Arial,"Trebuchet MS"}', expected: 'h1{font-family:Arial,sans-serif}' }, { message: 'should convert the font shorthand property', fixture: 'h1{font:italic small-caps normal 13px/150% "Helvetica Neue", sans-serif}', expected: 'h1{font:italic small-caps normal 13px/150% Helvetica Neue,sans-serif}' }, { message: 'should convert shorthand with zero unit line height', fixture: 'h1{font:italic small-caps normal 13px/1.5 "Helvetica Neue", sans-serif}', expected: 'h1{font:italic small-caps normal 13px/1.5 Helvetica Neue,sans-serif}', }, { message: 'should convert the font shorthand property, unquoted', fixture: 'h1{font:italic Helvetica Neue,sans-serif,Arial}', expected: 'h1{font:italic Helvetica Neue,sans-serif}' }, { message: 'should join identifiers in the shorthand property', fixture: 'h1{font:italic "Bond 007",sans-serif}', expected: 'h1{font:italic Bond\\ 007,sans-serif}' }, { message: 'should join non-digit identifiers in the shorthand property', fixture: 'h1{font:italic "Bond !",serif}', expected: 'h1{font:italic Bond\\ !,serif}' }, { message: 'should correctly escape special characters at the start', fixture: 'h1{font-family:"$42"}', expected: 'h1{font-family:\\$42}' }, { message: 'should not escape legal characters', fixture: 'h1{font-family:€42}', expected: 'h1{font-family:€42}' }, { message: 'should not join identifiers in the shorthand property', fixture: 'h1{font:italic "Bond 007 008 009",sans-serif}', expected: 'h1{font:italic "Bond 007 008 009",sans-serif}' }, { message: 'should escape special characters if unquoting', fixture: 'h1{font-family:"Ahem!"}', expected: 'h1{font-family:Ahem\\!}' }, { message: 'should not escape multiple special characters', fixture: 'h1{font-family:"Ahem!!"}', expected: 'h1{font-family:"Ahem!!"}' }, { message: 'should not mangle legal unquoted values', fixture: 'h1{font-family:\\$42}', expected: 'h1{font-family:\\$42}' }, { message: 'should not mangle font names', fixture: 'h1{font-family:Glyphicons Halflings}', expected: 'h1{font-family:Glyphicons Halflings}' }, { message: 'should not mangle font names (2)', fixture: 'h1{font-family:FF Din Pro,FF Din Pro Medium}', expected: 'h1{font-family:FF Din Pro,FF Din Pro Medium}' }, { message: 'should handle rem values', fixture: 'h1{font:bold 2.2rem/.9 "Open Sans Condensed", sans-serif}', expected: 'h1{font:700 2.2rem/.9 Open Sans Condensed,sans-serif}' }, { message: 'should pass through when it doesn\'t find a font property', fixture: 'h1{color:black;text-decoration:none}', expected: 'h1{color:black;text-decoration:none}' }, { message: 'should not remove duplicates', fixture: 'h1{font-family:Helvetica,Helvetica}', expected: 'h1{font-family:Helvetica,Helvetica}', options: {removeDuplicates: false} }, { message: 'should not remove after keyword', fixture: 'h1{font-family:serif,Times}', expected: 'h1{font-family:serif,Times}', options: {removeAfterKeyword: false} }, { message: 'should not remove quotes', fixture: 'h1{font-family:"Glyphicons Halflings", "Arial"}', expected: 'h1{font-family:"Glyphicons Halflings","Arial"}', options: {removeQuotes: false} }, { message: 'should not dedupe monospace', fixture: 'font-family:monospace,monospace', expected: 'font-family:monospace,monospace', }, { message: 'should not dedupe monospace (2)', fixture: 'font:italic small-caps normal 13px/150% monospace,monospace', expected: 'font:italic small-caps normal 13px/150% monospace,monospace' }, { message: 'should not mangle custom props', fixture: ':root{--sans:Helvetica}header{font-family:var(--sans)}', expected: ':root{--sans:Helvetica}header{font-family:var(--sans)}', }]; function process (css, options) { return postcss(plugin(options)).process(css).css; } test(name, function (t) { t.plan(tests.length); tests.forEach(function (test) { var options = test.options || {}; t.equal(process(test.fixture, options), test.expected, test.message); }); }); test('should use the postcss plugin api', function (t) { t.plan(2); t.ok(plugin().postcssVersion, 'should be able to access version'); t.equal(plugin().postcssPlugin, name, 'should be able to access name'); }); postcss-minify-font-values-1.0.5/test/minify-family.js000066400000000000000000000066661271444206400230560ustar00rootroot00000000000000var test = require('tape'); var minifyFamily = require('../lib/minify-family'); var tests = [ { message: 'Should strip quotes for names without keywords', options: { removeQuotes: true }, fixture: [ { type: 'space', value: ' ' }, { type: 'word', value: 'Times' }, { type: 'space', value: ' ' }, { type: 'word', value: 'new' }, { type: 'space', value: ' ' }, { type: 'word', value: 'Roman' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'word', value: 'sans-serif' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'string', quote: '"', value: 'serif' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'string', quote: '"', value: 'Roboto Plus' }, { type: 'div', value: ',', before: ' ', after: ' ' }, { type: 'word', value: 'Georgia' }, { type: 'space', value: ' ' } ], expected: [ { type: 'word', value: 'Times new Roman,sans-serif,"serif",Roboto Plus,Georgia' } ] }, { message: 'Should remove fonts after keywords', options: { removeAfterKeyword: true }, fixture: [ { type: 'space', value: ' ' }, { type: 'word', value: 'Times' }, { type: 'space', value: ' ' }, { type: 'word', value: 'new' }, { type: 'space', value: ' ' }, { type: 'word', value: 'Roman' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'string', quote: '"', value: 'serif' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'word', value: 'sans-serif' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'string', quote: '"', value: 'Roboto Plus' }, { type: 'div', value: ',', before: ' ', after: ' ' }, { type: 'word', value: 'Georgia' }, { type: 'space', value: ' ' } ], expected: [ { type: 'word', value: 'Times new Roman,"serif",sans-serif' } ] }, { message: 'Should dublicates', options: { removeQuotes: true, removeDuplicates: true }, fixture: [ { type: 'space', value: ' ' }, { type: 'word', value: 'Roman' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'string', quote: '"', value: 'serif' }, { type: 'word', value: 'Roman' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'word', value: 'serif' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'string', quote: '"', value: 'Roman' }, { type: 'div', value: ',', before: ' ', after: ' ' }, { type: 'word', value: 'Georgia' }, { type: 'space', value: ' ' } ], expected: [ { type: 'word', value: 'Roman,"serif",serif,Georgia' } ] } ]; test('minify-family', function (t) { t.plan(tests.length); tests.forEach(function (test) { t.deepEqual(minifyFamily(test.fixture, test.options), test.expected, test.message); }); }); postcss-minify-font-values-1.0.5/test/minify-font.js000066400000000000000000000024351271444206400225310ustar00rootroot00000000000000var test = require('tape'); var minifyFont = require('../lib/minify-font'); var tests = [ { options: {}, fixture: [ { type: 'word', value: 'bold' }, { type: 'space', value: ' ' }, { type: 'word', value: 'italic' }, { type: 'space', value: ' \t ' }, { type: 'word', value: '20px' }, { type: 'space', value: ' \n ' }, { type: 'word', value: 'Times' }, { type: 'space', value: ' ' }, { type: 'word', value: 'New' }, { type: 'space', value: ' \t ' }, { type: 'word', value: 'Roman' }, { type: 'div', value: ',', before: '', after: ' ' }, { type: 'word', value: 'serif' } ], expected: [ { type: 'word', value: '700' }, { type: 'space', value: ' ' }, { type: 'word', value: 'italic' }, { type: 'space', value: ' ' }, { type: 'word', value: '20px' }, { type: 'space', value: ' ' }, { type: 'word', value: 'Times New Roman,serif' } ] } ]; test('minify-font', function (t) { t.plan(tests.length); tests.forEach(function (test) { t.deepEqual(minifyFont(test.fixture, test.options), test.expected); }); }); postcss-minify-font-values-1.0.5/test/minify-weight.js000066400000000000000000000006711271444206400230520ustar00rootroot00000000000000var test = require('tape'); var minifyWeight = require('../lib/minify-weight'); test('minify-weight', function (t) { t.plan(4); t.equal(minifyWeight('normal'), '400', 'should convert normal -> 400'); t.equal(minifyWeight('bold'), '700', 'should convert bold -> 700'); t.equal(minifyWeight('lighter'), 'lighter', 'shouldn\'t convert lighter'); t.equal(minifyWeight('bolder'), 'bolder', 'shouldn\'t convert bolder'); });