Comparing version 4.2.0 to 5.0.0
@@ -1,5 +0,5 @@ | ||
var resolveKeyword = require('css-tree').keyword; | ||
var { hasNoChildren } = require('./utils'); | ||
import { keyword as resolveKeyword } from 'css-tree'; | ||
import { hasNoChildren } from './utils.js'; | ||
module.exports = function cleanAtrule(node, item, list) { | ||
export default function cleanAtrule(node, item, list) { | ||
if (node.block) { | ||
@@ -49,2 +49,3 @@ // otherwise removed at-rule don't prevent @import for removal | ||
list.remove(item); | ||
return true; | ||
@@ -55,4 +56,5 @@ }, this); | ||
default: | ||
var name = resolveKeyword(node.name).basename; | ||
default: { | ||
const name = resolveKeyword(node.name).basename; | ||
if (name === 'keyframes' || | ||
@@ -67,3 +69,4 @@ name === 'media' || | ||
} | ||
} | ||
} | ||
}; |
@@ -1,3 +0,3 @@ | ||
module.exports = function cleanComment(data, item, list) { | ||
export default function cleanComment(data, item, list) { | ||
list.remove(item); | ||
}; |
@@ -1,5 +0,5 @@ | ||
var property = require('css-tree').property; | ||
import { property } from 'css-tree'; | ||
module.exports = function cleanDeclartion(node, item, list) { | ||
if (node.value.children && node.value.children.isEmpty()) { | ||
export default function cleanDeclartion(node, item, list) { | ||
if (node.value.children && node.value.children.isEmpty) { | ||
list.remove(item); | ||
@@ -6,0 +6,0 @@ return; |
@@ -1,15 +0,23 @@ | ||
var walk = require('css-tree').walk; | ||
var handlers = { | ||
Atrule: require('./Atrule'), | ||
Comment: require('./Comment'), | ||
Declaration: require('./Declaration'), | ||
Raw: require('./Raw'), | ||
Rule: require('./Rule'), | ||
TypeSelector: require('./TypeSelector'), | ||
WhiteSpace: require('./WhiteSpace') | ||
import { walk } from 'css-tree'; | ||
import Atrule from './Atrule.js'; | ||
import Comment from './Comment.js'; | ||
import Declaration from './Declaration.js'; | ||
import Raw from './Raw.js'; | ||
import Rule from './Rule.js'; | ||
import TypeSelector from './TypeSelector.js'; | ||
import WhiteSpace from './WhiteSpace.js'; | ||
const handlers = { | ||
Atrule, | ||
Comment, | ||
Declaration, | ||
Raw, | ||
Rule, | ||
TypeSelector, | ||
WhiteSpace | ||
}; | ||
module.exports = function(ast, options) { | ||
export default function(ast, options) { | ||
walk(ast, { | ||
leave: function(node, item, list) { | ||
leave(node, item, list) { | ||
if (handlers.hasOwnProperty(node.type)) { | ||
@@ -16,0 +24,0 @@ handlers[node.type].call(this, node, item, list, options); |
@@ -1,4 +0,4 @@ | ||
var { isNodeChildrenList } = require('./utils'); | ||
import { isNodeChildrenList } from './utils.js'; | ||
module.exports = function cleanRaw(node, item, list) { | ||
export default function cleanRaw(node, item, list) { | ||
// raw in stylesheet or block children | ||
@@ -5,0 +5,0 @@ if (isNodeChildrenList(this.stylesheet, list) || |
@@ -1,8 +0,9 @@ | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
var walk = require('css-tree').walk; | ||
var { hasNoChildren } = require('./utils'); | ||
import { walk } from 'css-tree'; | ||
import { hasNoChildren } from './utils.js'; | ||
const { hasOwnProperty } = Object.prototype; | ||
function cleanUnused(selectorList, usageData) { | ||
selectorList.children.each(function(selector, item, list) { | ||
var shouldRemove = false; | ||
selectorList.children.forEach((selector, item, list) => { | ||
let shouldRemove = false; | ||
@@ -74,6 +75,6 @@ walk(selector, function(node) { | ||
return selectorList.children.isEmpty(); | ||
return selectorList.children.isEmpty; | ||
} | ||
module.exports = function cleanRule(node, item, list, options) { | ||
export default function cleanRule(node, item, list, options) { | ||
if (hasNoChildren(node.prelude) || hasNoChildren(node.block)) { | ||
@@ -84,6 +85,6 @@ list.remove(item); | ||
var usageData = options.usage; | ||
const { usage } = options; | ||
if (usageData && (usageData.whitelist !== null || usageData.blacklist !== null)) { | ||
cleanUnused(node.prelude, usageData); | ||
if (usage && (usage.whitelist !== null || usage.blacklist !== null)) { | ||
cleanUnused(node.prelude, usage); | ||
@@ -90,0 +91,0 @@ if (hasNoChildren(node.prelude)) { |
// remove useless universal selector | ||
module.exports = function cleanTypeSelector(node, item, list) { | ||
var name = item.data.name; | ||
export default function cleanTypeSelector(node, item, list) { | ||
const name = item.data.name; | ||
@@ -11,3 +11,3 @@ // check it's a non-namespaced universal selector | ||
// remove when universal selector before other selectors | ||
var nextType = item.next && item.next.data.type; | ||
const nextType = item.next && item.next.data.type; | ||
if (nextType === 'IdSelector' || | ||
@@ -14,0 +14,0 @@ nextType === 'ClassSelector' || |
@@ -1,8 +0,7 @@ | ||
module.exports = { | ||
hasNoChildren: function(node) { | ||
return !node || !node.children || node.children.isEmpty(); | ||
}, | ||
isNodeChildrenList: function(node, list) { | ||
return node !== null && node.children === list; | ||
} | ||
}; | ||
export function hasNoChildren(node) { | ||
return !node || !node.children || node.children.isEmpty; | ||
} | ||
export function isNodeChildrenList(node, list) { | ||
return node !== null && node.children === list; | ||
} |
@@ -1,30 +0,3 @@ | ||
var { isNodeChildrenList } = require('./utils'); | ||
function isSafeOperator(node) { | ||
return node.type === 'Operator' && node.value !== '+' && node.value !== '-'; | ||
} | ||
module.exports = function cleanWhitespace(node, item, list) { | ||
// remove when first or last item in sequence | ||
if (item.next === null || item.prev === null) { | ||
list.remove(item); | ||
return; | ||
} | ||
// white space in stylesheet or block children | ||
if (isNodeChildrenList(this.stylesheet, list) || | ||
isNodeChildrenList(this.block, list)) { | ||
list.remove(item); | ||
return; | ||
} | ||
if (item.next.data.type === 'WhiteSpace') { | ||
list.remove(item); | ||
return; | ||
} | ||
if (isSafeOperator(item.prev.data) || isSafeOperator(item.next.data)) { | ||
list.remove(item); | ||
return; | ||
} | ||
export default function cleanWhitespace(node, item, list) { | ||
list.remove(item); | ||
}; |
@@ -1,15 +0,13 @@ | ||
var List = require('css-tree').List; | ||
var clone = require('css-tree').clone; | ||
var usageUtils = require('./usage'); | ||
var clean = require('./clean'); | ||
var replace = require('./replace'); | ||
var restructure = require('./restructure'); | ||
var walk = require('css-tree').walk; | ||
import { List, clone, walk } from 'css-tree'; | ||
import { buildIndex } from './usage.js'; | ||
import clean from './clean/index.js'; | ||
import replace from './replace/index.js'; | ||
import restructure from './restructure/index.js'; | ||
function readChunk(children, specialComments) { | ||
var buffer = new List(); | ||
var nonSpaceTokenInBuffer = false; | ||
var protectedComment; | ||
function readChunk(input, specialComments) { | ||
const children = new List(); | ||
let nonSpaceTokenInBuffer = false; | ||
let protectedComment; | ||
children.nextUntil(children.head, function(node, item, list) { | ||
input.nextUntil(input.head, (node, item, list) => { | ||
if (node.type === 'Comment') { | ||
@@ -27,2 +25,3 @@ if (!specialComments || node.value.charAt(0) !== '!') { | ||
protectedComment = node; | ||
return; | ||
@@ -35,3 +34,3 @@ } | ||
buffer.insert(list.remove(item)); | ||
children.insert(list.remove(item)); | ||
}); | ||
@@ -44,3 +43,3 @@ | ||
loc: null, | ||
children: buffer | ||
children | ||
} | ||
@@ -51,5 +50,5 @@ }; | ||
function compressChunk(ast, firstAtrulesAllowed, num, options) { | ||
options.logger('Compress block #' + num, null, true); | ||
options.logger(`Compress block #${num}`, null, true); | ||
var seed = 1; | ||
let seed = 1; | ||
@@ -63,3 +62,3 @@ if (ast.type === 'StyleSheet') { | ||
visit: 'Atrule', | ||
enter: function markScopes(node) { | ||
enter(node) { | ||
if (node.block !== null) { | ||
@@ -89,3 +88,3 @@ node.block.id = seed++; | ||
function getCommentsOption(options) { | ||
var comments = 'comments' in options ? options.comments : 'exclamation'; | ||
let comments = 'comments' in options ? options.comments : 'exclamation'; | ||
@@ -126,23 +125,23 @@ if (typeof comments === 'boolean') { | ||
}, | ||
block: block | ||
block | ||
}); | ||
} | ||
module.exports = function compress(ast, options) { | ||
export default function compress(ast, options) { | ||
ast = ast || { type: 'StyleSheet', loc: null, children: new List() }; | ||
options = options || {}; | ||
var compressOptions = { | ||
const compressOptions = { | ||
logger: typeof options.logger === 'function' ? options.logger : function() {}, | ||
restructuring: getRestructureOption(options), | ||
forceMediaMerge: Boolean(options.forceMediaMerge), | ||
usage: options.usage ? usageUtils.buildIndex(options.usage) : false | ||
usage: options.usage ? buildIndex(options.usage) : false | ||
}; | ||
var specialComments = getCommentsOption(options); | ||
var firstAtrulesAllowed = true; | ||
var input; | ||
var output = new List(); | ||
var chunk; | ||
var chunkNum = 1; | ||
var chunkChildren; | ||
const output = new List(); | ||
let specialComments = getCommentsOption(options); | ||
let firstAtrulesAllowed = true; | ||
let input; | ||
let chunk; | ||
let chunkNum = 1; | ||
let chunkChildren; | ||
@@ -167,3 +166,3 @@ if (options.clone) { | ||
// add \n before comment if there is another content in output | ||
if (!output.isEmpty()) { | ||
if (!output.isEmpty) { | ||
output.insert(List.createItem({ | ||
@@ -178,3 +177,3 @@ type: 'Raw', | ||
// add \n after comment if chunk is not empty | ||
if (!chunkChildren.isEmpty()) { | ||
if (!chunkChildren.isEmpty) { | ||
output.insert(List.createItem({ | ||
@@ -187,4 +186,4 @@ type: 'Raw', | ||
if (firstAtrulesAllowed && !chunkChildren.isEmpty()) { | ||
var lastRule = chunkChildren.last(); | ||
if (firstAtrulesAllowed && !chunkChildren.isEmpty) { | ||
const lastRule = chunkChildren.last; | ||
@@ -202,7 +201,7 @@ if (lastRule.type !== 'Atrule' || | ||
output.appendList(chunkChildren); | ||
} while (!input.isEmpty()); | ||
} while (!input.isEmpty); | ||
return { | ||
ast: ast | ||
ast | ||
}; | ||
}; |
@@ -1,9 +0,10 @@ | ||
var csstree = require('css-tree'); | ||
var parse = csstree.parse; | ||
var compress = require('./compress'); | ||
var generate = csstree.generate; | ||
import { syntax } from './syntax.js'; | ||
import { version } from './version.js'; | ||
import * as utils from './utils.js'; | ||
const { parse, generate, compress } = syntax; | ||
function debugOutput(name, options, startTime, data) { | ||
if (options.debug) { | ||
console.error('## ' + name + ' done in %d ms\n', Date.now() - startTime); | ||
console.error(`## ${name} done in %d ms\n`, Date.now() - startTime); | ||
} | ||
@@ -15,13 +16,13 @@ | ||
function createDefaultLogger(level) { | ||
var lastDebug; | ||
let lastDebug; | ||
return function logger(title, ast) { | ||
var line = title; | ||
let line = title; | ||
if (ast) { | ||
line = '[' + ((Date.now() - lastDebug) / 1000).toFixed(3) + 's] ' + line; | ||
line = `[${((Date.now() - lastDebug) / 1000).toFixed(3)}s] ${line}`; | ||
} | ||
if (level > 1 && ast) { | ||
var css = generate(ast); | ||
let css = generate(ast); | ||
@@ -33,3 +34,3 @@ // when level 2, limit css to 256 symbols | ||
line += '\n ' + css + '\n'; | ||
line += `\n ${css}\n`; | ||
} | ||
@@ -42,14 +43,4 @@ | ||
function copy(obj) { | ||
var result = {}; | ||
for (var key in obj) { | ||
result[key] = obj[key]; | ||
} | ||
return result; | ||
} | ||
function buildCompressOptions(options) { | ||
options = copy(options); | ||
options = { ...options }; | ||
@@ -68,5 +59,3 @@ if (typeof options.logger !== 'function' && options.debug) { | ||
handlers.forEach(function(fn) { | ||
fn(ast, options); | ||
}); | ||
handlers.forEach(fn => fn(ast, options)); | ||
} | ||
@@ -77,10 +66,10 @@ | ||
var filename = options.filename || '<unknown>'; | ||
var result; | ||
const filename = options.filename || '<unknown>'; | ||
let result; | ||
// parse | ||
var ast = debugOutput('parsing', options, Date.now(), | ||
const ast = debugOutput('parsing', options, Date.now(), | ||
parse(source, { | ||
context: context, | ||
filename: filename, | ||
context, | ||
filename, | ||
positions: Boolean(options.sourceMap) | ||
@@ -98,3 +87,3 @@ }) | ||
// compress | ||
var compressResult = debugOutput('compress', options, Date.now(), | ||
const compressResult = debugOutput('compress', options, Date.now(), | ||
compress(ast, buildCompressOptions(options)) | ||
@@ -112,8 +101,10 @@ ); | ||
if (options.sourceMap) { | ||
result = debugOutput('generate(sourceMap: true)', options, Date.now(), (function() { | ||
var tmp = generate(compressResult.ast, { sourceMap: true }); | ||
result = debugOutput('generate(sourceMap: true)', options, Date.now(), (() => { | ||
const tmp = generate(compressResult.ast, { sourceMap: true }); | ||
tmp.map._file = filename; // since other tools can relay on file in source map transform chain | ||
tmp.map.setSourceContent(filename, source); | ||
return tmp; | ||
}())); | ||
})()); | ||
} else { | ||
@@ -137,13 +128,12 @@ result = debugOutput('generate', options, Date.now(), { | ||
module.exports = { | ||
version: require('../package.json').version, | ||
export { | ||
version, | ||
utils, | ||
// main methods | ||
minify: minifyStylesheet, | ||
minifyBlock: minifyBlock, | ||
minifyStylesheet as minify, | ||
minifyBlock, | ||
// css syntax parser/walkers/generator/etc | ||
syntax: Object.assign({ | ||
compress: compress | ||
}, csstree) | ||
syntax | ||
}; |
@@ -1,5 +0,5 @@ | ||
var resolveKeyword = require('css-tree').keyword; | ||
var compressKeyframes = require('./atrule/keyframes'); | ||
import { keyword as resolveKeyword } from 'css-tree'; | ||
import compressKeyframes from './atrule/keyframes.js'; | ||
module.exports = function(node) { | ||
export default function(node) { | ||
// compress @keyframe selectors | ||
@@ -6,0 +6,0 @@ if (resolveKeyword(node.name).basename === 'keyframes') { |
@@ -1,5 +0,5 @@ | ||
module.exports = function(node) { | ||
node.block.children.each(function(rule) { | ||
rule.prelude.children.each(function(simpleselector) { | ||
simpleselector.children.each(function(data, item) { | ||
export default function(node) { | ||
node.block.children.forEach((rule) => { | ||
rule.prelude.children.forEach((simpleselector) => { | ||
simpleselector.children.forEach((data, item) => { | ||
if (data.type === 'Percentage' && data.value === '100') { | ||
@@ -6,0 +6,0 @@ item.data = { |
// Can unquote attribute detection | ||
// Adopted implementation of Mathias Bynens | ||
// https://github.com/mathiasbynens/mothereff.in/blob/master/unquoted-attributes/eff.js | ||
var escapesRx = /\\([0-9A-Fa-f]{1,6})(\r\n|[ \t\n\f\r])?|\\./g; | ||
var blockUnquoteRx = /^(-?\d|--)|[\u0000-\u002c\u002e\u002f\u003A-\u0040\u005B-\u005E\u0060\u007B-\u009f]/; | ||
const blockUnquoteRx = /^(-?\d|--)|[\u0000-\u002c\u002e\u002f\u003A-\u0040\u005B-\u005E\u0060\u007B-\u009f]/; | ||
function canUnquote(value) { | ||
if (value === '' || value === '-') { | ||
return; | ||
return false; | ||
} | ||
// Escapes are valid, so replace them with a valid non-empty string | ||
value = value.replace(escapesRx, 'a'); | ||
return !blockUnquoteRx.test(value); | ||
} | ||
module.exports = function(node) { | ||
var attrValue = node.value; | ||
export default function(node) { | ||
const attrValue = node.value; | ||
@@ -25,10 +21,9 @@ if (!attrValue || attrValue.type !== 'String') { | ||
var unquotedValue = attrValue.value.replace(/^(.)(.*)\1$/, '$2'); | ||
if (canUnquote(unquotedValue)) { | ||
if (canUnquote(attrValue.value)) { | ||
node.value = { | ||
type: 'Identifier', | ||
loc: attrValue.loc, | ||
name: unquotedValue | ||
name: attrValue.value | ||
}; | ||
} | ||
}; |
@@ -1,6 +0,6 @@ | ||
var lexer = require('css-tree').lexer; | ||
var packNumber = require('./Number').pack; | ||
import { lexer } from 'css-tree'; | ||
import { packNumber } from './Number.js'; | ||
// http://www.w3.org/TR/css3-color/#svg-color | ||
var NAME_TO_HEX = { | ||
const NAME_TO_HEX = { | ||
'aliceblue': 'f0f8ff', | ||
@@ -156,3 +156,3 @@ 'antiquewhite': 'faebd7', | ||
var HEX_TO_NAME = { | ||
const HEX_TO_NAME = { | ||
'800000': 'maroon', | ||
@@ -218,5 +218,5 @@ '800080': 'purple', | ||
function hslToRgb(h, s, l, a) { | ||
var r; | ||
var g; | ||
var b; | ||
let r; | ||
let g; | ||
let b; | ||
@@ -226,4 +226,4 @@ if (s === 0) { | ||
} else { | ||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s; | ||
var p = 2 * l - q; | ||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | ||
const p = 2 * l - q; | ||
@@ -245,2 +245,3 @@ r = hueToRgb(p, q, h + 1 / 3); | ||
value = value.toString(16); | ||
return value.length === 1 ? '0' + value : value; | ||
@@ -250,9 +251,8 @@ } | ||
function parseFunctionArgs(functionArgs, count, rgb) { | ||
var cursor = functionArgs.head; | ||
var args = []; | ||
var wasValue = false; | ||
let cursor = functionArgs.head; | ||
let args = []; | ||
let wasValue = false; | ||
while (cursor !== null) { | ||
var node = cursor.data; | ||
var type = node.type; | ||
const { type, value } = cursor.data; | ||
@@ -268,9 +268,10 @@ switch (type) { | ||
args.push({ | ||
type: type, | ||
value: Number(node.value) | ||
type, | ||
value: Number(value) | ||
}); | ||
break; | ||
case 'Operator': | ||
if (node.value === ',') { | ||
if (value === ',') { | ||
if (!wasValue) { | ||
@@ -280,5 +281,6 @@ return; | ||
wasValue = false; | ||
} else if (wasValue || node.value !== '+') { | ||
} else if (wasValue || value !== '+') { | ||
return; | ||
} | ||
break; | ||
@@ -329,3 +331,3 @@ | ||
return args.map(function(arg) { | ||
var value = Math.max(0, arg.value); | ||
let value = Math.max(0, arg.value); | ||
@@ -362,5 +364,5 @@ switch (arg.type) { | ||
function compressFunction(node, item, list) { | ||
var functionName = node.name; | ||
var args; | ||
export function compressFunction(node, item) { | ||
let functionName = node.name; | ||
let args; | ||
@@ -376,3 +378,3 @@ if (functionName === 'rgba' || functionName === 'hsla') { | ||
if (functionName === 'hsla') { | ||
args = hslToRgb.apply(null, args); | ||
args = hslToRgb(...args); | ||
node.name = 'rgba'; | ||
@@ -386,3 +388,4 @@ } | ||
// http://stackoverflow.com/questions/11829410/css3-gradient-rendering-issues-from-transparent-to-white | ||
var scopeFunctionName = this.function && this.function.name; | ||
const scopeFunctionName = this.function && this.function.name; | ||
if ((args[0] === 0 && args[1] === 0 && args[2] === 0) || | ||
@@ -403,3 +406,3 @@ !/^(?:to|from|color-stop)$|gradient$/i.test(scopeFunctionName)) { | ||
// replace argument values for normalized/interpolated | ||
node.children.each(function(node, item, list) { | ||
node.children.forEach((node, item, list) => { | ||
if (node.type === 'Operator') { | ||
@@ -415,3 +418,3 @@ if (node.value !== ',') { | ||
loc: node.loc, | ||
value: packNumber(args.shift(), null) | ||
value: packNumber(args.shift()) | ||
}; | ||
@@ -436,3 +439,3 @@ }); | ||
// convert to rgb | ||
args = hslToRgb.apply(null, args); | ||
args = hslToRgb(...args); | ||
functionName = 'rgb'; | ||
@@ -449,11 +452,2 @@ } | ||
// check if color is not at the end and not followed by space | ||
var next = item.next; | ||
if (next && next.data.type !== 'WhiteSpace') { | ||
list.insert(list.createItem({ | ||
type: 'WhiteSpace', | ||
value: ' ' | ||
}), next); | ||
} | ||
item.data = { | ||
@@ -469,3 +463,3 @@ type: 'Hash', | ||
function compressIdent(node, item) { | ||
export function compressIdent(node, item) { | ||
if (this.declaration === null) { | ||
@@ -475,7 +469,7 @@ return; | ||
var color = node.name.toLowerCase(); | ||
let color = node.name.toLowerCase(); | ||
if (NAME_TO_HEX.hasOwnProperty(color) && | ||
lexer.matchDeclaration(this.declaration).isType(node, 'color')) { | ||
var hex = NAME_TO_HEX[color]; | ||
const hex = NAME_TO_HEX[color]; | ||
@@ -501,4 +495,4 @@ if (hex.length + 1 <= color.length) { | ||
function compressHex(node, item) { | ||
var color = node.value.toLowerCase(); | ||
export function compressHex(node, item) { | ||
let color = node.value.toLowerCase(); | ||
@@ -523,7 +517,1 @@ // #112233 -> #123 | ||
} | ||
module.exports = { | ||
compressFunction: compressFunction, | ||
compressIdent: compressIdent, | ||
compressHex: compressHex | ||
}; |
@@ -1,33 +0,34 @@ | ||
var packNumber = require('./Number').pack; | ||
var MATH_FUNCTIONS = { | ||
'calc': true, | ||
'min': true, | ||
'max': true, | ||
'clamp': true | ||
}; | ||
var LENGTH_UNIT = { | ||
import { packNumber } from './Number.js'; | ||
const MATH_FUNCTIONS = new Set([ | ||
'calc', | ||
'min', | ||
'max', | ||
'clamp' | ||
]); | ||
const LENGTH_UNIT = new Set([ | ||
// absolute length units | ||
'px': true, | ||
'mm': true, | ||
'cm': true, | ||
'in': true, | ||
'pt': true, | ||
'pc': true, | ||
'px', | ||
'mm', | ||
'cm', | ||
'in', | ||
'pt', | ||
'pc', | ||
// relative length units | ||
'em': true, | ||
'ex': true, | ||
'ch': true, | ||
'rem': true, | ||
'em', | ||
'ex', | ||
'ch', | ||
'rem', | ||
// viewport-percentage lengths | ||
'vh': true, | ||
'vw': true, | ||
'vmin': true, | ||
'vmax': true, | ||
'vm': true | ||
}; | ||
'vh', | ||
'vw', | ||
'vmin', | ||
'vmax', | ||
'vm' | ||
]); | ||
module.exports = function compressDimension(node, item) { | ||
var value = packNumber(node.value, item); | ||
export default function compressDimension(node, item) { | ||
const value = packNumber(node.value); | ||
@@ -37,6 +38,6 @@ node.value = value; | ||
if (value === '0' && this.declaration !== null && this.atrulePrelude === null) { | ||
var unit = node.unit.toLowerCase(); | ||
const unit = node.unit.toLowerCase(); | ||
// only length values can be compressed | ||
if (!LENGTH_UNIT.hasOwnProperty(unit)) { | ||
if (!LENGTH_UNIT.has(unit)) { | ||
return; | ||
@@ -53,3 +54,3 @@ } | ||
// issue #222: don't remove units inside calc | ||
if (this.function && MATH_FUNCTIONS.hasOwnProperty(this.function.name)) { | ||
if (this.function && MATH_FUNCTIONS.has(this.function.name)) { | ||
return; | ||
@@ -61,5 +62,5 @@ } | ||
loc: node.loc, | ||
value: value | ||
value | ||
}; | ||
} | ||
}; |
@@ -1,19 +0,27 @@ | ||
var walk = require('css-tree').walk; | ||
var handlers = { | ||
Atrule: require('./Atrule'), | ||
AttributeSelector: require('./AttributeSelector'), | ||
Value: require('./Value'), | ||
Dimension: require('./Dimension'), | ||
Percentage: require('./Percentage'), | ||
Number: require('./Number'), | ||
String: require('./String'), | ||
Url: require('./Url'), | ||
Hash: require('./color').compressHex, | ||
Identifier: require('./color').compressIdent, | ||
Function: require('./color').compressFunction | ||
import { walk } from 'css-tree'; | ||
import Atrule from './Atrule.js'; | ||
import AttributeSelector from './AttributeSelector.js'; | ||
import Value from './Value.js'; | ||
import Dimension from './Dimension.js'; | ||
import Percentage from './Percentage.js'; | ||
import { Number } from './Number.js'; | ||
import Url from './Url.js'; | ||
import { compressHex, compressIdent, compressFunction } from './color.js'; | ||
const handlers = { | ||
Atrule, | ||
AttributeSelector, | ||
Value, | ||
Dimension, | ||
Percentage, | ||
Number, | ||
Url, | ||
Hash: compressHex, | ||
Identifier: compressIdent, | ||
Function: compressFunction | ||
}; | ||
module.exports = function(ast) { | ||
export default function(ast) { | ||
walk(ast, { | ||
leave: function(node, item, list) { | ||
leave(node, item, list) { | ||
if (handlers.hasOwnProperty(node.type)) { | ||
@@ -20,0 +28,0 @@ handlers[node.type].call(this, node, item, list); |
@@ -1,15 +0,15 @@ | ||
var OMIT_PLUSSIGN = /^(?:\+|(-))?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/; | ||
var KEEP_PLUSSIGN = /^([\+\-])?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/; | ||
var unsafeToRemovePlusSignAfter = { | ||
Dimension: true, | ||
Hash: true, | ||
Identifier: true, | ||
Number: true, | ||
Raw: true, | ||
UnicodeRange: true | ||
}; | ||
const OMIT_PLUSSIGN = /^(?:\+|(-))?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/; | ||
const KEEP_PLUSSIGN = /^([\+\-])?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/; | ||
const unsafeToRemovePlusSignAfter = new Set([ | ||
'Dimension', | ||
'Hash', | ||
'Identifier', | ||
'Number', | ||
'Raw', | ||
'UnicodeRange' | ||
]); | ||
function packNumber(value, item) { | ||
export function packNumber(value, item) { | ||
// omit plus sign only if no prev or prev is safe type | ||
var regexp = item && item.prev !== null && unsafeToRemovePlusSignAfter.hasOwnProperty(item.prev.data.type) | ||
const regexp = item && item.prev !== null && unsafeToRemovePlusSignAfter.has(item.prev.data.type) | ||
? KEEP_PLUSSIGN | ||
@@ -20,3 +20,3 @@ : OMIT_PLUSSIGN; | ||
// 00100 -> '100' | ||
// +100 -> '100' (only when safe, e.g. omitting plus sign for 1px+1px leads to single dimension instead of two) | ||
// +100 -> '100' | ||
// -100 -> '-100' | ||
@@ -33,2 +33,4 @@ // 0.123 -> '.123' | ||
} | ||
// FIXME: is it solution simplier? | ||
// value = String(Number(value)).replace(/^(-?)0+\./, '$1.'); | ||
@@ -38,5 +40,4 @@ return value; | ||
module.exports = function(node, item) { | ||
node.value = packNumber(node.value, item); | ||
export function Number(node) { | ||
node.value = packNumber(node.value); | ||
}; | ||
module.exports.pack = packNumber; |
@@ -1,4 +0,5 @@ | ||
var lexer = require('css-tree').lexer; | ||
var packNumber = require('./Number').pack; | ||
var blacklist = new Set([ | ||
import { lexer } from 'css-tree'; | ||
import { packNumber } from './Number.js'; | ||
const blacklist = new Set([ | ||
// see https://github.com/jakubpawlowicz/clean-css/issues/957 | ||
@@ -19,4 +20,4 @@ 'width', | ||
module.exports = function compressPercentage(node, item) { | ||
node.value = packNumber(node.value, item); | ||
export default function compressPercentage(node, item) { | ||
node.value = packNumber(node.value); | ||
@@ -23,0 +24,0 @@ if (node.value === '0' && this.declaration && !blacklist.has(this.declaration.property)) { |
@@ -1,15 +0,5 @@ | ||
var List = require('css-tree').List; | ||
import { List } from 'css-tree'; | ||
module.exports = function compressBackground(node) { | ||
function lastType() { | ||
if (buffer.length) { | ||
return buffer[buffer.length - 1].type; | ||
} | ||
} | ||
export default function compressBackground(node) { | ||
function flush() { | ||
if (lastType() === 'WhiteSpace') { | ||
buffer.pop(); | ||
} | ||
if (!buffer.length) { | ||
@@ -23,6 +13,2 @@ buffer.unshift( | ||
{ | ||
type: 'WhiteSpace', | ||
value: ' ' | ||
}, | ||
{ | ||
type: 'Number', | ||
@@ -40,6 +26,6 @@ loc: null, | ||
var newValue = []; | ||
var buffer = []; | ||
let newValue = []; | ||
let buffer = []; | ||
node.children.each(function(node) { | ||
node.children.forEach((node) => { | ||
if (node.type === 'Operator' && node.value === ',') { | ||
@@ -61,7 +47,2 @@ flush(); | ||
// don't add redundant spaces | ||
if (node.type === 'WhiteSpace' && (!buffer.length || lastType() === 'WhiteSpace')) { | ||
return; | ||
} | ||
buffer.push(node); | ||
@@ -68,0 +49,0 @@ }); |
@@ -1,18 +0,3 @@ | ||
function removeItemAndRedundantWhiteSpace(list, item) { | ||
var prev = item.prev; | ||
var next = item.next; | ||
if (next !== null) { | ||
if (next.data.type === 'WhiteSpace' && (prev === null || prev.data.type === 'WhiteSpace')) { | ||
list.remove(next); | ||
} | ||
} else if (prev !== null && prev.data.type === 'WhiteSpace') { | ||
list.remove(prev); | ||
} | ||
list.remove(item); | ||
} | ||
module.exports = function compressBorder(node) { | ||
node.children.each(function(node, item, list) { | ||
export default function compressBorder(node) { | ||
node.children.forEach((node, item, list) => { | ||
if (node.type === 'Identifier' && node.name.toLowerCase() === 'none') { | ||
@@ -27,3 +12,3 @@ if (list.head === list.tail) { | ||
} else { | ||
removeItemAndRedundantWhiteSpace(list, item); | ||
list.remove(item); | ||
} | ||
@@ -30,0 +15,0 @@ } |
@@ -1,3 +0,3 @@ | ||
module.exports = function compressFontWeight(node) { | ||
var value = node.children.head.data; | ||
export default function compressFontWeight(node) { | ||
const value = node.children.head.data; | ||
@@ -4,0 +4,0 @@ if (value.type === 'Identifier') { |
@@ -1,5 +0,5 @@ | ||
module.exports = function compressFont(node) { | ||
var list = node.children; | ||
export default function compressFont(node) { | ||
const list = node.children; | ||
list.eachRight(function(node, item) { | ||
list.forEachRight(function(node, item) { | ||
if (node.type === 'Identifier') { | ||
@@ -13,3 +13,3 @@ if (node.name === 'bold') { | ||
} else if (node.name === 'normal') { | ||
var prev = item.prev; | ||
const prev = item.prev; | ||
@@ -22,3 +22,3 @@ if (prev && prev.data.type === 'Operator' && prev.data.value === '/') { | ||
} else if (node.name === 'medium') { | ||
var next = item.next; | ||
const next = item.next; | ||
@@ -32,12 +32,3 @@ if (!next || next.data.type !== 'Operator') { | ||
// remove redundant spaces | ||
list.each(function(node, item) { | ||
if (node.type === 'WhiteSpace') { | ||
if (!item.prev || !item.next || item.next.data.type === 'WhiteSpace') { | ||
this.remove(item); | ||
} | ||
} | ||
}); | ||
if (list.isEmpty()) { | ||
if (list.isEmpty) { | ||
list.insert(list.createItem({ | ||
@@ -44,0 +35,0 @@ type: 'Identifier', |
@@ -1,33 +0,4 @@ | ||
var UNICODE = '\\\\[0-9a-f]{1,6}(\\r\\n|[ \\n\\r\\t\\f])?'; | ||
var ESCAPE = '(' + UNICODE + '|\\\\[^\\n\\r\\f0-9a-fA-F])'; | ||
var NONPRINTABLE = '\u0000\u0008\u000b\u000e-\u001f\u007f'; | ||
var SAFE_URL = new RegExp('^(' + ESCAPE + '|[^\"\'\\(\\)\\\\\\s' + NONPRINTABLE + '])*$', 'i'); | ||
module.exports = function(node) { | ||
var value = node.value; | ||
if (value.type !== 'String') { | ||
return; | ||
} | ||
var quote = value.value[0]; | ||
var url = value.value.substr(1, value.value.length - 2); | ||
export default function(node) { | ||
// convert `\\` to `/` | ||
url = url.replace(/\\\\/g, '/'); | ||
// remove quotes when safe | ||
// https://www.w3.org/TR/css-syntax-3/#url-unquoted-diagram | ||
if (SAFE_URL.test(url)) { | ||
node.value = { | ||
type: 'Raw', | ||
loc: node.value.loc, | ||
value: url | ||
}; | ||
} else { | ||
// use double quotes if string has no double quotes | ||
// otherwise use original quotes | ||
// TODO: make better quote type selection | ||
node.value.value = url.indexOf('"') === -1 ? '"' + url + '"' : quote + url + quote; | ||
} | ||
node.value = node.value.replace(/\\/g, '/'); | ||
}; |
@@ -1,11 +0,17 @@ | ||
var resolveName = require('css-tree').property; | ||
var handlers = { | ||
'font': require('./property/font'), | ||
'font-weight': require('./property/font-weight'), | ||
'background': require('./property/background'), | ||
'border': require('./property/border'), | ||
'outline': require('./property/border') | ||
import { property as resolveName } from 'css-tree'; | ||
import font from './property/font.js'; | ||
import fontWeight from './property/font-weight.js'; | ||
import background from './property/background.js'; | ||
import border from './property/border.js'; | ||
import outline from './property/border.js'; | ||
const handlers = { | ||
'font': font, | ||
'font-weight': fontWeight, | ||
'background': background, | ||
'border': border, | ||
'outline': outline | ||
}; | ||
module.exports = function compressValue(node) { | ||
export default function compressValue(node) { | ||
if (!this.declaration) { | ||
@@ -15,3 +21,3 @@ return; | ||
var property = resolveName(this.declaration.property); | ||
const property = resolveName(this.declaration.property); | ||
@@ -18,0 +24,0 @@ if (handlers.hasOwnProperty(property.basename)) { |
@@ -1,10 +0,9 @@ | ||
var List = require('css-tree').List; | ||
var resolveKeyword = require('css-tree').keyword; | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
var walk = require('css-tree').walk; | ||
import { List, walk, keyword as resolveKeyword } from 'css-tree'; | ||
const { hasOwnProperty } = Object.prototype; | ||
function addRuleToMap(map, item, list, single) { | ||
var node = item.data; | ||
var name = resolveKeyword(node.name).basename; | ||
var id = node.name.toLowerCase() + '/' + (node.prelude ? node.prelude.id : null); | ||
const node = item.data; | ||
const name = resolveKeyword(node.name).basename; | ||
const id = node.name.toLowerCase() + '/' + (node.prelude ? node.prelude.id : null); | ||
@@ -27,8 +26,8 @@ if (!hasOwnProperty.call(map, name)) { | ||
function relocateAtrules(ast, options) { | ||
var collected = Object.create(null); | ||
var topInjectPoint = null; | ||
const collected = Object.create(null); | ||
let topInjectPoint = null; | ||
ast.children.each(function(node, item, list) { | ||
ast.children.forEach(function(node, item, list) { | ||
if (node.type === 'Atrule') { | ||
var name = resolveKeyword(node.name).basename; | ||
const name = resolveKeyword(node.name).basename; | ||
@@ -60,4 +59,4 @@ switch (name) { | ||
for (var atrule in collected) { | ||
for (var id in collected[atrule]) { | ||
for (const atrule in collected) { | ||
for (const id in collected[atrule]) { | ||
ast.children.insertList( | ||
@@ -80,3 +79,3 @@ collected[atrule][id], | ||
var prev = item.prev && item.prev.data; | ||
const prev = item.prev && item.prev.data; | ||
@@ -102,3 +101,3 @@ if (!prev || !isMediaRule(prev)) { | ||
module.exports = function rejoinAtrule(ast, options) { | ||
export default function rejoinAtrule(ast, options) { | ||
relocateAtrules(ast, options); | ||
@@ -105,0 +104,0 @@ |
@@ -1,7 +0,13 @@ | ||
var walk = require('css-tree').walk; | ||
var utils = require('./utils'); | ||
import { walk } from 'css-tree'; | ||
import { | ||
unsafeToSkipNode, | ||
isEqualSelectors, | ||
isEqualDeclarations, | ||
addSelectors, | ||
hasSimilarSelectors | ||
} from './utils.js'; | ||
function processRule(node, item, list) { | ||
var selectors = node.prelude.children; | ||
var declarations = node.block.children; | ||
const selectors = node.prelude.children; | ||
const declarations = node.block.children; | ||
@@ -11,7 +17,7 @@ list.prevUntil(item.prev, function(prev) { | ||
if (prev.type !== 'Rule') { | ||
return utils.unsafeToSkipNode.call(selectors, prev); | ||
return unsafeToSkipNode.call(selectors, prev); | ||
} | ||
var prevSelectors = prev.prelude.children; | ||
var prevDeclarations = prev.block.children; | ||
const prevSelectors = prev.prelude.children; | ||
const prevDeclarations = prev.block.children; | ||
@@ -21,3 +27,3 @@ // try to join rulesets with equal pseudo signature | ||
// try to join by selectors | ||
if (utils.isEqualSelectors(prevSelectors, selectors)) { | ||
if (isEqualSelectors(prevSelectors, selectors)) { | ||
prevDeclarations.appendList(declarations); | ||
@@ -29,4 +35,4 @@ list.remove(item); | ||
// try to join by declarations | ||
if (utils.isEqualDeclarations(declarations, prevDeclarations)) { | ||
utils.addSelectors(prevSelectors, selectors); | ||
if (isEqualDeclarations(declarations, prevDeclarations)) { | ||
addSelectors(prevSelectors, selectors); | ||
list.remove(item); | ||
@@ -38,3 +44,3 @@ return true; | ||
// go to prev ruleset if has no selector similarities | ||
return utils.hasSimilarSelectors(selectors, prevSelectors); | ||
return hasSimilarSelectors(selectors, prevSelectors); | ||
}); | ||
@@ -46,3 +52,3 @@ } | ||
// TODO: remove initial merge | ||
module.exports = function initialMergeRule(ast) { | ||
export default function initialMergeRule(ast) { | ||
walk(ast, { | ||
@@ -49,0 +55,0 @@ visit: 'Rule', |
@@ -1,6 +0,5 @@ | ||
var List = require('css-tree').List; | ||
var walk = require('css-tree').walk; | ||
import { List, walk } from 'css-tree'; | ||
function processRule(node, item, list) { | ||
var selectors = node.prelude.children; | ||
const selectors = node.prelude.children; | ||
@@ -15,3 +14,4 @@ // generate new rule sets: | ||
while (selectors.head !== selectors.tail) { | ||
var newSelectors = new List(); | ||
const newSelectors = new List(); | ||
newSelectors.insert(selectors.remove(selectors.head)); | ||
@@ -37,3 +37,3 @@ | ||
module.exports = function disjoinRule(ast) { | ||
export default function disjoinRule(ast) { | ||
walk(ast, { | ||
@@ -40,0 +40,0 @@ visit: 'Rule', |
@@ -1,13 +0,11 @@ | ||
var List = require('css-tree').List; | ||
var generate = require('css-tree').generate; | ||
var walk = require('css-tree').walk; | ||
import { List, generate, walk } from 'css-tree'; | ||
var REPLACE = 1; | ||
var REMOVE = 2; | ||
var TOP = 0; | ||
var RIGHT = 1; | ||
var BOTTOM = 2; | ||
var LEFT = 3; | ||
var SIDES = ['top', 'right', 'bottom', 'left']; | ||
var SIDE = { | ||
const REPLACE = 1; | ||
const REMOVE = 2; | ||
const TOP = 0; | ||
const RIGHT = 1; | ||
const BOTTOM = 2; | ||
const LEFT = 3; | ||
const SIDES = ['top', 'right', 'bottom', 'left']; | ||
const SIDE = { | ||
'margin-top': 'top', | ||
@@ -36,3 +34,3 @@ 'margin-right': 'right', | ||
}; | ||
var MAIN_PROPERTY = { | ||
const MAIN_PROPERTY = { | ||
'margin': 'margin', | ||
@@ -67,277 +65,272 @@ 'margin-top': 'margin', | ||
function TRBL(name) { | ||
this.name = name; | ||
this.loc = null; | ||
this.iehack = undefined; | ||
this.sides = { | ||
'top': null, | ||
'right': null, | ||
'bottom': null, | ||
'left': null | ||
}; | ||
} | ||
class TRBL { | ||
constructor(name) { | ||
this.name = name; | ||
this.loc = null; | ||
this.iehack = undefined; | ||
this.sides = { | ||
'top': null, | ||
'right': null, | ||
'bottom': null, | ||
'left': null | ||
}; | ||
} | ||
TRBL.prototype.getValueSequence = function(declaration, count) { | ||
var values = []; | ||
var iehack = ''; | ||
var hasBadValues = declaration.value.type !== 'Value' || declaration.value.children.some(function(child) { | ||
var special = false; | ||
getValueSequence(declaration, count) { | ||
const values = []; | ||
let iehack = ''; | ||
const hasBadValues = declaration.value.type !== 'Value' || declaration.value.children.some(function(child) { | ||
let special = false; | ||
switch (child.type) { | ||
case 'Identifier': | ||
switch (child.name) { | ||
case '\\0': | ||
case '\\9': | ||
iehack = child.name; | ||
return; | ||
switch (child.type) { | ||
case 'Identifier': | ||
switch (child.name) { | ||
case '\\0': | ||
case '\\9': | ||
iehack = child.name; | ||
return; | ||
case 'inherit': | ||
case 'initial': | ||
case 'unset': | ||
case 'revert': | ||
special = child.name; | ||
break; | ||
} | ||
break; | ||
case 'inherit': | ||
case 'initial': | ||
case 'unset': | ||
case 'revert': | ||
special = child.name; | ||
break; | ||
} | ||
break; | ||
case 'Dimension': | ||
switch (child.unit) { | ||
// is not supported until IE11 | ||
case 'rem': | ||
case 'Dimension': | ||
switch (child.unit) { | ||
// is not supported until IE11 | ||
case 'rem': | ||
// v* units is too buggy across browsers and better | ||
// don't merge values with those units | ||
case 'vw': | ||
case 'vh': | ||
case 'vmin': | ||
case 'vmax': | ||
case 'vm': // IE9 supporting "vm" instead of "vmin". | ||
special = child.unit; | ||
break; | ||
} | ||
break; | ||
// v* units is too buggy across browsers and better | ||
// don't merge values with those units | ||
case 'vw': | ||
case 'vh': | ||
case 'vmin': | ||
case 'vmax': | ||
case 'vm': // IE9 supporting "vm" instead of "vmin". | ||
special = child.unit; | ||
break; | ||
} | ||
break; | ||
case 'Hash': // color | ||
case 'Number': | ||
case 'Percentage': | ||
break; | ||
case 'Hash': // color | ||
case 'Number': | ||
case 'Percentage': | ||
break; | ||
case 'Function': | ||
if (child.name === 'var') { | ||
return true; | ||
} | ||
case 'Function': | ||
if (child.name === 'var') { | ||
return true; | ||
} | ||
special = child.name; | ||
break; | ||
special = child.name; | ||
break; | ||
case 'WhiteSpace': | ||
return false; // ignore space | ||
default: | ||
return true; // bad value | ||
} | ||
default: | ||
return true; // bad value | ||
values.push({ | ||
node: child, | ||
special, | ||
important: declaration.important | ||
}); | ||
}); | ||
if (hasBadValues || values.length > count) { | ||
return false; | ||
} | ||
values.push({ | ||
node: child, | ||
special: special, | ||
important: declaration.important | ||
}); | ||
}); | ||
if (typeof this.iehack === 'string' && this.iehack !== iehack) { | ||
return false; | ||
} | ||
if (hasBadValues || values.length > count) { | ||
return false; | ||
this.iehack = iehack; // move outside | ||
return values; | ||
} | ||
if (typeof this.iehack === 'string' && this.iehack !== iehack) { | ||
return false; | ||
canOverride(side, value) { | ||
const currentValue = this.sides[side]; | ||
return !currentValue || (value.important && !currentValue.important); | ||
} | ||
this.iehack = iehack; // move outside | ||
add(name, declaration) { | ||
function attemptToAdd() { | ||
const sides = this.sides; | ||
const side = SIDE[name]; | ||
return values; | ||
}; | ||
if (side) { | ||
if (side in sides === false) { | ||
return false; | ||
} | ||
TRBL.prototype.canOverride = function(side, value) { | ||
var currentValue = this.sides[side]; | ||
const values = this.getValueSequence(declaration, 1); | ||
return !currentValue || (value.important && !currentValue.important); | ||
}; | ||
if (!values || !values.length) { | ||
return false; | ||
} | ||
TRBL.prototype.add = function(name, declaration) { | ||
function attemptToAdd() { | ||
var sides = this.sides; | ||
var side = SIDE[name]; | ||
// can mix only if specials are equal | ||
for (const key in sides) { | ||
if (sides[key] !== null && sides[key].special !== values[0].special) { | ||
return false; | ||
} | ||
} | ||
if (side) { | ||
if (side in sides === false) { | ||
return false; | ||
} | ||
if (!this.canOverride(side, values[0])) { | ||
return true; | ||
} | ||
var values = this.getValueSequence(declaration, 1); | ||
sides[side] = values[0]; | ||
if (!values || !values.length) { | ||
return false; | ||
} | ||
return true; | ||
} else if (name === this.name) { | ||
const values = this.getValueSequence(declaration, 4); | ||
// can mix only if specials are equal | ||
for (var key in sides) { | ||
if (sides[key] !== null && sides[key].special !== values[0].special) { | ||
if (!values || !values.length) { | ||
return false; | ||
} | ||
} | ||
if (!this.canOverride(side, values[0])) { | ||
return true; | ||
} | ||
switch (values.length) { | ||
case 1: | ||
values[RIGHT] = values[TOP]; | ||
values[BOTTOM] = values[TOP]; | ||
values[LEFT] = values[TOP]; | ||
break; | ||
sides[side] = values[0]; | ||
return true; | ||
} else if (name === this.name) { | ||
var values = this.getValueSequence(declaration, 4); | ||
case 2: | ||
values[BOTTOM] = values[TOP]; | ||
values[LEFT] = values[RIGHT]; | ||
break; | ||
if (!values || !values.length) { | ||
return false; | ||
} | ||
case 3: | ||
values[LEFT] = values[RIGHT]; | ||
break; | ||
} | ||
switch (values.length) { | ||
case 1: | ||
values[RIGHT] = values[TOP]; | ||
values[BOTTOM] = values[TOP]; | ||
values[LEFT] = values[TOP]; | ||
break; | ||
// can mix only if specials are equal | ||
for (let i = 0; i < 4; i++) { | ||
for (const key in sides) { | ||
if (sides[key] !== null && sides[key].special !== values[i].special) { | ||
return false; | ||
} | ||
} | ||
} | ||
case 2: | ||
values[BOTTOM] = values[TOP]; | ||
values[LEFT] = values[RIGHT]; | ||
break; | ||
case 3: | ||
values[LEFT] = values[RIGHT]; | ||
break; | ||
} | ||
// can mix only if specials are equal | ||
for (var i = 0; i < 4; i++) { | ||
for (var key in sides) { | ||
if (sides[key] !== null && sides[key].special !== values[i].special) { | ||
return false; | ||
for (let i = 0; i < 4; i++) { | ||
if (this.canOverride(SIDES[i], values[i])) { | ||
sides[SIDES[i]] = values[i]; | ||
} | ||
} | ||
} | ||
for (var i = 0; i < 4; i++) { | ||
if (this.canOverride(SIDES[i], values[i])) { | ||
sides[SIDES[i]] = values[i]; | ||
} | ||
return true; | ||
} | ||
} | ||
return true; | ||
if (!attemptToAdd.call(this)) { | ||
return false; | ||
} | ||
} | ||
if (!attemptToAdd.call(this)) { | ||
return false; | ||
} | ||
// TODO: use it when we can refer to several points in source | ||
// if (this.loc) { | ||
// this.loc = { | ||
// primary: this.loc, | ||
// merged: declaration.loc | ||
// }; | ||
// } else { | ||
// this.loc = declaration.loc; | ||
// } | ||
if (!this.loc) { | ||
this.loc = declaration.loc; | ||
} | ||
// TODO: use it when we can refer to several points in source | ||
// if (this.loc) { | ||
// this.loc = { | ||
// primary: this.loc, | ||
// merged: declaration.loc | ||
// }; | ||
// } else { | ||
// this.loc = declaration.loc; | ||
// } | ||
if (!this.loc) { | ||
this.loc = declaration.loc; | ||
return true; | ||
} | ||
return true; | ||
}; | ||
isOkToMinimize() { | ||
const top = this.sides.top; | ||
const right = this.sides.right; | ||
const bottom = this.sides.bottom; | ||
const left = this.sides.left; | ||
TRBL.prototype.isOkToMinimize = function() { | ||
var top = this.sides.top; | ||
var right = this.sides.right; | ||
var bottom = this.sides.bottom; | ||
var left = this.sides.left; | ||
if (top && right && bottom && left) { | ||
const important = | ||
top.important + | ||
right.important + | ||
bottom.important + | ||
left.important; | ||
if (top && right && bottom && left) { | ||
var important = | ||
top.important + | ||
right.important + | ||
bottom.important + | ||
left.important; | ||
return important === 0 || important === 4; | ||
} | ||
return important === 0 || important === 4; | ||
return false; | ||
} | ||
return false; | ||
}; | ||
getValue() { | ||
const result = new List(); | ||
const sides = this.sides; | ||
const values = [ | ||
sides.top, | ||
sides.right, | ||
sides.bottom, | ||
sides.left | ||
]; | ||
const stringValues = [ | ||
generate(sides.top.node), | ||
generate(sides.right.node), | ||
generate(sides.bottom.node), | ||
generate(sides.left.node) | ||
]; | ||
TRBL.prototype.getValue = function() { | ||
var result = new List(); | ||
var sides = this.sides; | ||
var values = [ | ||
sides.top, | ||
sides.right, | ||
sides.bottom, | ||
sides.left | ||
]; | ||
var stringValues = [ | ||
generate(sides.top.node), | ||
generate(sides.right.node), | ||
generate(sides.bottom.node), | ||
generate(sides.left.node) | ||
]; | ||
if (stringValues[LEFT] === stringValues[RIGHT]) { | ||
values.pop(); | ||
if (stringValues[BOTTOM] === stringValues[TOP]) { | ||
if (stringValues[LEFT] === stringValues[RIGHT]) { | ||
values.pop(); | ||
if (stringValues[RIGHT] === stringValues[TOP]) { | ||
if (stringValues[BOTTOM] === stringValues[TOP]) { | ||
values.pop(); | ||
if (stringValues[RIGHT] === stringValues[TOP]) { | ||
values.pop(); | ||
} | ||
} | ||
} | ||
} | ||
for (var i = 0; i < values.length; i++) { | ||
if (i) { | ||
result.appendData({ type: 'WhiteSpace', value: ' ' }); | ||
for (let i = 0; i < values.length; i++) { | ||
result.appendData(values[i].node); | ||
} | ||
result.appendData(values[i].node); | ||
} | ||
if (this.iehack) { | ||
result.appendData({ | ||
type: 'Identifier', | ||
loc: null, | ||
name: this.iehack | ||
}); | ||
} | ||
if (this.iehack) { | ||
result.appendData({ type: 'WhiteSpace', value: ' ' }); | ||
result.appendData({ | ||
type: 'Identifier', | ||
return { | ||
type: 'Value', | ||
loc: null, | ||
name: this.iehack | ||
}); | ||
children: result | ||
}; | ||
} | ||
return { | ||
type: 'Value', | ||
loc: null, | ||
children: result | ||
}; | ||
}; | ||
getDeclaration() { | ||
return { | ||
type: 'Declaration', | ||
loc: this.loc, | ||
important: this.sides.top.important, | ||
property: this.name, | ||
value: this.getValue() | ||
}; | ||
} | ||
} | ||
TRBL.prototype.getDeclaration = function() { | ||
return { | ||
type: 'Declaration', | ||
loc: this.loc, | ||
important: this.sides.top.important, | ||
property: this.name, | ||
value: this.getValue() | ||
}; | ||
}; | ||
function processRule(rule, shorts, shortDeclarations, lastShortSelector) { | ||
var declarations = rule.block.children; | ||
var selector = rule.prelude.children.first().id; | ||
const declarations = rule.block.children; | ||
const selector = rule.prelude.children.first.id; | ||
rule.block.children.eachRight(function(declaration, item) { | ||
var property = declaration.property; | ||
rule.block.children.forEachRight(function(declaration, item) { | ||
const property = declaration.property; | ||
@@ -348,5 +341,5 @@ if (!MAIN_PROPERTY.hasOwnProperty(property)) { | ||
var key = MAIN_PROPERTY[property]; | ||
var shorthand; | ||
var operation; | ||
const key = MAIN_PROPERTY[property]; | ||
let shorthand; | ||
let operation; | ||
@@ -373,6 +366,6 @@ if (!lastShortSelector || selector === lastShortSelector) { | ||
shortDeclarations.push({ | ||
operation: operation, | ||
operation, | ||
block: declarations, | ||
item: item, | ||
shorthand: shorthand | ||
item, | ||
shorthand | ||
}); | ||
@@ -388,3 +381,3 @@ | ||
shortDeclarations.forEach(function(item) { | ||
var shorthand = item.shorthand; | ||
const shorthand = item.shorthand; | ||
@@ -403,5 +396,5 @@ if (!shorthand.isOkToMinimize()) { | ||
module.exports = function restructBlock(ast, indexer) { | ||
var stylesheetMap = {}; | ||
var shortDeclarations = []; | ||
export default function restructBlock(ast, indexer) { | ||
const stylesheetMap = {}; | ||
const shortDeclarations = []; | ||
@@ -411,7 +404,7 @@ walk(ast, { | ||
reverse: true, | ||
enter: function(node) { | ||
var stylesheet = this.block || this.stylesheet; | ||
var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id; | ||
var ruleMap; | ||
var shorts; | ||
enter(node) { | ||
const stylesheet = this.block || this.stylesheet; | ||
const ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first.id; | ||
let ruleMap; | ||
let shorts; | ||
@@ -418,0 +411,0 @@ if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { |
@@ -1,11 +0,14 @@ | ||
var resolveProperty = require('css-tree').property; | ||
var resolveKeyword = require('css-tree').keyword; | ||
var walk = require('css-tree').walk; | ||
var generate = require('css-tree').generate; | ||
var fingerprintId = 1; | ||
var dontRestructure = { | ||
'src': 1 // https://github.com/afelix/csso/issues/50 | ||
}; | ||
import { | ||
walk, | ||
generate, | ||
property as resolveProperty, | ||
keyword as resolveKeyword | ||
} from 'css-tree'; | ||
var DONT_MIX_VALUE = { | ||
let fingerprintId = 1; | ||
const dontRestructure = new Set([ | ||
'src' // https://github.com/afelix/csso/issues/50 | ||
]); | ||
const DONT_MIX_VALUE = { | ||
// https://developer.mozilla.org/en-US/docs/Web/CSS/display#Browser_compatibility | ||
@@ -17,3 +20,3 @@ 'display': /table|ruby|flex|-(flex)?box$|grid|contents|run-in/i, | ||
var SAFE_VALUES = { | ||
const SAFE_VALUES = { | ||
cursor: [ | ||
@@ -34,3 +37,3 @@ 'auto', 'crosshair', 'default', 'move', 'text', 'wait', 'help', | ||
var NEEDLESS_TABLE = { | ||
const NEEDLESS_TABLE = { | ||
'border-width': ['border'], | ||
@@ -74,3 +77,3 @@ 'border-style': ['border'], | ||
function getPropertyFingerprint(propertyName, declaration, fingerprints) { | ||
var realName = resolveProperty(propertyName).basename; | ||
const realName = resolveProperty(propertyName).basename; | ||
@@ -81,4 +84,4 @@ if (realName === 'background') { | ||
var declarationId = declaration.id; | ||
var fingerprint = fingerprints[declarationId]; | ||
const declarationId = declaration.id; | ||
let fingerprint = fingerprints[declarationId]; | ||
@@ -88,8 +91,8 @@ if (!fingerprint) { | ||
case 'Value': | ||
var vendorId = ''; | ||
var iehack = ''; | ||
var special = {}; | ||
var raw = false; | ||
const special = {}; | ||
let vendorId = ''; | ||
let iehack = ''; | ||
let raw = false; | ||
declaration.value.children.each(function walk(node) { | ||
declaration.value.children.forEach(function walk(node) { | ||
switch (node.type) { | ||
@@ -99,3 +102,3 @@ case 'Value': | ||
case 'Parentheses': | ||
node.children.each(walk); | ||
node.children.forEach(walk); | ||
break; | ||
@@ -107,4 +110,4 @@ | ||
case 'Identifier': | ||
var name = node.name; | ||
case 'Identifier': { | ||
const { name } = node; | ||
@@ -130,5 +133,6 @@ if (!vendorId) { | ||
break; | ||
} | ||
case 'Function': | ||
var name = node.name; | ||
case 'Function': { | ||
let { name } = node; | ||
@@ -144,5 +148,6 @@ if (!vendorId) { | ||
// only the same form values can be merged | ||
var hasComma = node.children.some(function(node) { | ||
return node.type === 'Operator' && node.value === ','; | ||
}); | ||
const hasComma = node.children.some((node) => | ||
node.type === 'Operator' && node.value === ',' | ||
); | ||
if (!hasComma) { | ||
@@ -156,8 +161,9 @@ name = 'rect-backward'; | ||
// check nested tokens too | ||
node.children.each(walk); | ||
node.children.forEach(walk); | ||
break; | ||
} | ||
case 'Dimension': | ||
var unit = node.unit; | ||
case 'Dimension': { | ||
const { unit } = node; | ||
@@ -182,3 +188,5 @@ if (/\\[09]/.test(unit)) { | ||
} | ||
break; | ||
} | ||
} | ||
@@ -207,10 +215,10 @@ }); | ||
function needless(props, declaration, fingerprints) { | ||
var property = resolveProperty(declaration.property); | ||
const property = resolveProperty(declaration.property); | ||
if (NEEDLESS_TABLE.hasOwnProperty(property.basename)) { | ||
var table = NEEDLESS_TABLE[property.basename]; | ||
const table = NEEDLESS_TABLE[property.basename]; | ||
for (var i = 0; i < table.length; i++) { | ||
var ppre = getPropertyFingerprint(property.prefix + table[i], declaration, fingerprints); | ||
var prev = props.hasOwnProperty(ppre) ? props[ppre] : null; | ||
for (const entry of table) { | ||
const ppre = getPropertyFingerprint(property.prefix + entry, declaration, fingerprints); | ||
const prev = props.hasOwnProperty(ppre) ? props[ppre] : null; | ||
@@ -225,10 +233,10 @@ if (prev && (!declaration.important || prev.item.data.important)) { | ||
function processRule(rule, item, list, props, fingerprints) { | ||
var declarations = rule.block.children; | ||
const declarations = rule.block.children; | ||
declarations.eachRight(function(declaration, declarationItem) { | ||
var property = declaration.property; | ||
var fingerprint = getPropertyFingerprint(property, declaration, fingerprints); | ||
var prev = props[fingerprint]; | ||
declarations.forEachRight(function(declaration, declarationItem) { | ||
const { property } = declaration; | ||
const fingerprint = getPropertyFingerprint(property, declaration, fingerprints); | ||
const prev = props[fingerprint]; | ||
if (prev && !dontRestructure.hasOwnProperty(property)) { | ||
if (prev && !dontRestructure.has(property)) { | ||
if (declaration.important && !prev.item.data.important) { | ||
@@ -257,3 +265,3 @@ props[fingerprint] = { | ||
} else { | ||
var prev = needless(props, declaration, fingerprints); | ||
const prev = needless(props, declaration, fingerprints); | ||
@@ -279,3 +287,3 @@ if (prev) { | ||
if (declarations.isEmpty()) { | ||
if (declarations.isEmpty) { | ||
list.remove(item); | ||
@@ -285,5 +293,5 @@ } | ||
module.exports = function restructBlock(ast) { | ||
var stylesheetMap = {}; | ||
var fingerprints = Object.create(null); | ||
export default function restructBlock(ast) { | ||
const stylesheetMap = {}; | ||
const fingerprints = Object.create(null); | ||
@@ -293,7 +301,7 @@ walk(ast, { | ||
reverse: true, | ||
enter: function(node, item, list) { | ||
var stylesheet = this.block || this.stylesheet; | ||
var ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first().id; | ||
var ruleMap; | ||
var props; | ||
enter(node, item, list) { | ||
const stylesheet = this.block || this.stylesheet; | ||
const ruleId = (node.pseudoSignature || '') + '|' + node.prelude.children.first.id; | ||
let ruleMap; | ||
let props; | ||
@@ -300,0 +308,0 @@ if (!stylesheetMap.hasOwnProperty(stylesheet.id)) { |
@@ -1,3 +0,3 @@ | ||
var walk = require('css-tree').walk; | ||
var utils = require('./utils'); | ||
import { walk } from 'css-tree'; | ||
import { unsafeToSkipNode, isEqualDeclarations} from './utils.js'; | ||
@@ -17,6 +17,6 @@ /* | ||
function processRule(node, item, list) { | ||
var selectors = node.prelude.children; | ||
var declarations = node.block.children; | ||
var nodeCompareMarker = selectors.first().compareMarker; | ||
var skippedCompareMarkers = {}; | ||
const selectors = node.prelude.children; | ||
const declarations = node.block.children; | ||
const nodeCompareMarker = selectors.first.compareMarker; | ||
const skippedCompareMarkers = {}; | ||
@@ -26,3 +26,3 @@ list.nextUntil(item.next, function(next, nextItem) { | ||
if (next.type !== 'Rule') { | ||
return utils.unsafeToSkipNode.call(selectors, next); | ||
return unsafeToSkipNode.call(selectors, next); | ||
} | ||
@@ -34,5 +34,5 @@ | ||
var nextFirstSelector = next.prelude.children.head; | ||
var nextDeclarations = next.block.children; | ||
var nextCompareMarker = nextFirstSelector.data.compareMarker; | ||
const nextFirstSelector = next.prelude.children.head; | ||
const nextDeclarations = next.block.children; | ||
const nextCompareMarker = nextFirstSelector.data.compareMarker; | ||
@@ -46,3 +46,3 @@ // if next ruleset has same marked as one of skipped then stop joining | ||
if (selectors.head === selectors.tail) { | ||
if (selectors.first().id === nextFirstSelector.data.id) { | ||
if (selectors.first.id === nextFirstSelector.data.id) { | ||
declarations.appendList(nextDeclarations); | ||
@@ -55,7 +55,7 @@ list.remove(nextItem); | ||
// try to join by properties | ||
if (utils.isEqualDeclarations(declarations, nextDeclarations)) { | ||
var nextStr = nextFirstSelector.data.id; | ||
if (isEqualDeclarations(declarations, nextDeclarations)) { | ||
const nextStr = nextFirstSelector.data.id; | ||
selectors.some(function(data, item) { | ||
var curStr = data.id; | ||
selectors.some((data, item) => { | ||
const curStr = data.id; | ||
@@ -86,3 +86,3 @@ if (nextStr < curStr) { | ||
module.exports = function mergeRule(ast) { | ||
export default function mergeRule(ast) { | ||
walk(ast, { | ||
@@ -89,0 +89,0 @@ visit: 'Rule', |
@@ -1,20 +0,18 @@ | ||
var List = require('css-tree').List; | ||
var walk = require('css-tree').walk; | ||
var utils = require('./utils'); | ||
import { List, walk } from 'css-tree'; | ||
import { | ||
unsafeToSkipNode, | ||
isEqualSelectors, | ||
compareDeclarations, | ||
addSelectors | ||
} from './utils.js'; | ||
function calcSelectorLength(list) { | ||
var length = 0; | ||
list.each(function(data) { | ||
length += data.id.length + 1; | ||
}); | ||
return length - 1; | ||
return list.reduce((res, data) => res + data.id.length + 1, 0) - 1; | ||
} | ||
function calcDeclarationsLength(tokens) { | ||
var length = 0; | ||
let length = 0; | ||
for (var i = 0; i < tokens.length; i++) { | ||
length += tokens[i].length; | ||
for (const token of tokens) { | ||
length += token.length; | ||
} | ||
@@ -29,15 +27,15 @@ | ||
function processRule(node, item, list) { | ||
var avoidRulesMerge = this.block !== null ? this.block.avoidRulesMerge : false; | ||
var selectors = node.prelude.children; | ||
var block = node.block; | ||
var disallowDownMarkers = Object.create(null); | ||
var allowMergeUp = true; | ||
var allowMergeDown = true; | ||
const avoidRulesMerge = this.block !== null ? this.block.avoidRulesMerge : false; | ||
const selectors = node.prelude.children; | ||
const block = node.block; | ||
const disallowDownMarkers = Object.create(null); | ||
let allowMergeUp = true; | ||
let allowMergeDown = true; | ||
list.prevUntil(item.prev, function(prev, prevItem) { | ||
var prevBlock = prev.block; | ||
var prevType = prev.type; | ||
const prevBlock = prev.block; | ||
const prevType = prev.type; | ||
if (prevType !== 'Rule') { | ||
var unsafe = utils.unsafeToSkipNode.call(selectors, prev); | ||
const unsafe = unsafeToSkipNode.call(selectors, prev); | ||
@@ -47,4 +45,4 @@ if (!unsafe && prevType === 'Atrule' && prevBlock) { | ||
visit: 'Rule', | ||
enter: function(node) { | ||
node.prelude.children.each(function(data) { | ||
enter(node) { | ||
node.prelude.children.forEach((data) => { | ||
disallowDownMarkers[data.compareMarker] = true; | ||
@@ -59,4 +57,2 @@ }); | ||
var prevSelectors = prev.prelude.children; | ||
if (node.pseudoSignature !== prev.pseudoSignature) { | ||
@@ -66,6 +62,8 @@ return true; | ||
allowMergeDown = !prevSelectors.some(function(selector) { | ||
return selector.compareMarker in disallowDownMarkers; | ||
}); | ||
const prevSelectors = prev.prelude.children; | ||
allowMergeDown = !prevSelectors.some((selector) => | ||
selector.compareMarker in disallowDownMarkers | ||
); | ||
// try prev ruleset if simpleselectors has no equal specifity and element selector | ||
@@ -77,5 +75,6 @@ if (!allowMergeDown && !allowMergeUp) { | ||
// try to join by selectors | ||
if (allowMergeUp && utils.isEqualSelectors(prevSelectors, selectors)) { | ||
if (allowMergeUp && isEqualSelectors(prevSelectors, selectors)) { | ||
prevBlock.children.appendList(block.children); | ||
list.remove(item); | ||
return true; | ||
@@ -85,3 +84,3 @@ } | ||
// try to join by properties | ||
var diff = utils.compareDeclarations(block.children, prevBlock.children); | ||
const diff = compareDeclarations(block.children, prevBlock.children); | ||
@@ -94,3 +93,3 @@ // console.log(diff.eq, diff.ne1, diff.ne2); | ||
if (allowMergeDown) { | ||
utils.addSelectors(selectors, prevSelectors); | ||
addSelectors(selectors, prevSelectors); | ||
list.remove(prevItem); | ||
@@ -105,17 +104,17 @@ } | ||
// prevBlock is subset block | ||
var selectorLength = calcSelectorLength(selectors); | ||
var blockLength = calcDeclarationsLength(diff.eq); // declarations length | ||
const selectorLength = calcSelectorLength(selectors); | ||
const blockLength = calcDeclarationsLength(diff.eq); // declarations length | ||
if (allowMergeUp && selectorLength < blockLength) { | ||
utils.addSelectors(prevSelectors, selectors); | ||
block.children = new List().fromArray(diff.ne1); | ||
addSelectors(prevSelectors, selectors); | ||
block.children.fromArray(diff.ne1); | ||
} | ||
} else if (!diff.ne1.length && diff.ne2.length) { | ||
// node is subset of prevBlock | ||
var selectorLength = calcSelectorLength(prevSelectors); | ||
var blockLength = calcDeclarationsLength(diff.eq); // declarations length | ||
const selectorLength = calcSelectorLength(prevSelectors); | ||
const blockLength = calcDeclarationsLength(diff.eq); // declarations length | ||
if (allowMergeDown && selectorLength < blockLength) { | ||
utils.addSelectors(selectors, prevSelectors); | ||
prevBlock.children = new List().fromArray(diff.ne2); | ||
addSelectors(selectors, prevSelectors); | ||
prevBlock.children.fromArray(diff.ne2); | ||
} | ||
@@ -125,9 +124,9 @@ } else { | ||
// extract equal block | ||
var newSelector = { | ||
const newSelector = { | ||
type: 'SelectorList', | ||
loc: null, | ||
children: utils.addSelectors(prevSelectors.copy(), selectors) | ||
children: addSelectors(prevSelectors.copy(), selectors) | ||
}; | ||
var newBlockLength = calcSelectorLength(newSelector.children) + 2; // selectors length + curly braces length | ||
var blockLength = calcDeclarationsLength(diff.eq); // declarations length | ||
const newBlockLength = calcSelectorLength(newSelector.children) + 2; // selectors length + curly braces length | ||
const blockLength = calcDeclarationsLength(diff.eq); // declarations length | ||
@@ -137,3 +136,3 @@ // create new ruleset if declarations length greater than | ||
if (blockLength >= newBlockLength) { | ||
var newItem = list.createItem({ | ||
const newItem = list.createItem({ | ||
type: 'Rule', | ||
@@ -150,4 +149,4 @@ loc: null, | ||
block.children = new List().fromArray(diff.ne1); | ||
prevBlock.children = new List().fromArray(diff.ne2overrided); | ||
block.children.fromArray(diff.ne1); | ||
prevBlock.children.fromArray(diff.ne2overrided); | ||
@@ -169,10 +168,10 @@ if (allowMergeUp) { | ||
// await property families to find property interception correctly | ||
allowMergeUp = !prevSelectors.some(function(prevSelector) { | ||
return selectors.some(function(selector) { | ||
return selector.compareMarker === prevSelector.compareMarker; | ||
}); | ||
}); | ||
allowMergeUp = !prevSelectors.some((prevSelector) => | ||
selectors.some((selector) => | ||
selector.compareMarker === prevSelector.compareMarker | ||
) | ||
); | ||
} | ||
prevSelectors.each(function(data) { | ||
prevSelectors.forEach((data) => { | ||
disallowDownMarkers[data.compareMarker] = true; | ||
@@ -183,3 +182,3 @@ }); | ||
module.exports = function restructRule(ast) { | ||
export default function restructRule(ast) { | ||
walk(ast, { | ||
@@ -186,0 +185,0 @@ visit: 'Rule', |
@@ -1,13 +0,13 @@ | ||
var prepare = require('./prepare/index'); | ||
var mergeAtrule = require('./1-mergeAtrule'); | ||
var initialMergeRuleset = require('./2-initialMergeRuleset'); | ||
var disjoinRuleset = require('./3-disjoinRuleset'); | ||
var restructShorthand = require('./4-restructShorthand'); | ||
var restructBlock = require('./6-restructBlock'); | ||
var mergeRuleset = require('./7-mergeRuleset'); | ||
var restructRuleset = require('./8-restructRuleset'); | ||
import prepare from './prepare/index.js'; | ||
import mergeAtrule from './1-mergeAtrule.js'; | ||
import initialMergeRuleset from './2-initialMergeRuleset.js'; | ||
import disjoinRuleset from './3-disjoinRuleset.js'; | ||
import restructShorthand from './4-restructShorthand.js'; | ||
import restructBlock from './6-restructBlock.js'; | ||
import mergeRuleset from './7-mergeRuleset.js'; | ||
import restructRuleset from './8-restructRuleset.js'; | ||
module.exports = function(ast, options) { | ||
export default function(ast, options) { | ||
// prepare ast for restructing | ||
var indexer = prepare(ast, options); | ||
const indexer = prepare(ast, options); | ||
options.logger('prepare', ast); | ||
@@ -14,0 +14,0 @@ |
@@ -1,24 +0,24 @@ | ||
var generate = require('css-tree').generate; | ||
import { generate } from 'css-tree'; | ||
function Index() { | ||
this.seed = 0; | ||
this.map = Object.create(null); | ||
} | ||
class Index { | ||
constructor() { | ||
this.map = new Map(); | ||
} | ||
resolve(str) { | ||
let index = this.map.get(str); | ||
Index.prototype.resolve = function(str) { | ||
var index = this.map[str]; | ||
if (index === undefined) { | ||
index = this.map.size + 1; | ||
this.map.set(str, index); | ||
} | ||
if (!index) { | ||
index = ++this.seed; | ||
this.map[str] = index; | ||
return index; | ||
} | ||
return index; | ||
}; | ||
module.exports = function createDeclarationIndexer() { | ||
var ids = new Index(); | ||
export default function createDeclarationIndexer() { | ||
const ids = new Index(); | ||
return function markDeclaration(node) { | ||
var id = generate(node); | ||
const id = generate(node); | ||
@@ -25,0 +25,0 @@ node.id = ids.resolve(id); |
@@ -1,14 +0,12 @@ | ||
var resolveKeyword = require('css-tree').keyword; | ||
var walk = require('css-tree').walk; | ||
var generate = require('css-tree').generate; | ||
var createDeclarationIndexer = require('./createDeclarationIndexer'); | ||
var processSelector = require('./processSelector'); | ||
import { walk, generate, keyword as resolveKeyword } from 'css-tree'; | ||
import createDeclarationIndexer from './createDeclarationIndexer.js'; | ||
import processSelector from './processSelector.js'; | ||
module.exports = function prepare(ast, options) { | ||
var markDeclaration = createDeclarationIndexer(); | ||
export default function prepare(ast, options) { | ||
const markDeclaration = createDeclarationIndexer(); | ||
walk(ast, { | ||
visit: 'Rule', | ||
enter: function processRule(node) { | ||
node.block.children.each(markDeclaration); | ||
enter(node) { | ||
node.block.children.forEach(markDeclaration); | ||
processSelector(node, options.usage); | ||
@@ -20,3 +18,3 @@ } | ||
visit: 'Atrule', | ||
enter: function(node) { | ||
enter(node) { | ||
if (node.prelude) { | ||
@@ -32,4 +30,4 @@ node.prelude.id = null; // pre-init property to avoid multiple hidden class for generate | ||
TODO: need to be checked */ | ||
node.block.children.each(function(rule) { | ||
rule.prelude.children.each(function(simpleselector) { | ||
node.block.children.forEach(function(rule) { | ||
rule.prelude.children.forEach(function(simpleselector) { | ||
simpleselector.compareMarker = simpleselector.id; | ||
@@ -36,0 +34,0 @@ }); |
@@ -1,34 +0,33 @@ | ||
var generate = require('css-tree').generate; | ||
var specificity = require('./specificity'); | ||
import { generate } from 'css-tree'; | ||
import specificity from './specificity.js'; | ||
var nonFreezePseudoElements = { | ||
'first-letter': true, | ||
'first-line': true, | ||
'after': true, | ||
'before': true | ||
}; | ||
var nonFreezePseudoClasses = { | ||
'link': true, | ||
'visited': true, | ||
'hover': true, | ||
'active': true, | ||
'first-letter': true, | ||
'first-line': true, | ||
'after': true, | ||
'before': true | ||
}; | ||
const nonFreezePseudoElements = new Set([ | ||
'first-letter', | ||
'first-line', | ||
'after', | ||
'before' | ||
]); | ||
const nonFreezePseudoClasses = new Set([ | ||
'link', | ||
'visited', | ||
'hover', | ||
'active', | ||
'first-letter', | ||
'first-line', | ||
'after', | ||
'before' | ||
]); | ||
module.exports = function freeze(node, usageData) { | ||
var pseudos = Object.create(null); | ||
var hasPseudo = false; | ||
export default function processSelector(node, usageData) { | ||
const pseudos = new Set(); | ||
node.prelude.children.each(function(simpleSelector) { | ||
var tagName = '*'; | ||
var scope = 0; | ||
node.prelude.children.forEach(function(simpleSelector) { | ||
let tagName = '*'; | ||
let scope = 0; | ||
simpleSelector.children.each(function(node) { | ||
simpleSelector.children.forEach(function(node) { | ||
switch (node.type) { | ||
case 'ClassSelector': | ||
if (usageData && usageData.scopes) { | ||
var classScope = usageData.scopes[node.name] || 0; | ||
const classScope = usageData.scopes[node.name] || 0; | ||
@@ -41,21 +40,24 @@ if (scope !== 0 && classScope !== scope) { | ||
} | ||
break; | ||
case 'PseudoClassSelector': | ||
var name = node.name.toLowerCase(); | ||
case 'PseudoClassSelector': { | ||
const name = node.name.toLowerCase(); | ||
if (!nonFreezePseudoClasses.hasOwnProperty(name)) { | ||
pseudos[':' + name] = true; | ||
hasPseudo = true; | ||
if (!nonFreezePseudoClasses.has(name)) { | ||
pseudos.add(`:${name}`); | ||
} | ||
break; | ||
} | ||
case 'PseudoElementSelector': | ||
var name = node.name.toLowerCase(); | ||
case 'PseudoElementSelector': { | ||
const name = node.name.toLowerCase(); | ||
if (!nonFreezePseudoElements.hasOwnProperty(name)) { | ||
pseudos['::' + name] = true; | ||
hasPseudo = true; | ||
if (!nonFreezePseudoElements.has(name)) { | ||
pseudos.add(`::${name}`); | ||
} | ||
break; | ||
} | ||
@@ -68,8 +70,7 @@ case 'TypeSelector': | ||
if (node.flags) { | ||
pseudos['[' + node.flags.toLowerCase() + ']'] = true; | ||
hasPseudo = true; | ||
pseudos.add(`[${node.flags.toLowerCase()}]`); | ||
} | ||
break; | ||
case 'WhiteSpace': | ||
case 'Combinator': | ||
@@ -95,3 +96,5 @@ tagName = '*'; | ||
// add property to all rule nodes to avoid multiple hidden class | ||
node.pseudoSignature = hasPseudo && Object.keys(pseudos).sort().join(','); | ||
node.pseudoSignature = pseudos.size > 0 | ||
? [...pseudos].sort().join(',') | ||
: false; | ||
}; |
@@ -1,13 +0,39 @@ | ||
module.exports = function specificity(simpleSelector) { | ||
var A = 0; | ||
var B = 0; | ||
var C = 0; | ||
import { parse } from 'css-tree'; | ||
simpleSelector.children.each(function walk(node) { | ||
function ensureSelectorList(node) { | ||
if (node.type === 'Raw') { | ||
return parse(node.value, { context: 'selectorList' }); | ||
} | ||
return node; | ||
} | ||
function maxSpecificity(a, b) { | ||
for (let i = 0; i < 3; i++) { | ||
if (a[i] !== b[i]) { | ||
return a[i] > b[i] ? a : b; | ||
} | ||
} | ||
return a; | ||
} | ||
function maxSelectorListSpecificity(selectorList) { | ||
return ensureSelectorList(selectorList).children.reduce( | ||
(result, node) => maxSpecificity(specificity(node), result), | ||
[0, 0, 0] | ||
); | ||
} | ||
// §16. Calculating a selector’s specificity | ||
// https://www.w3.org/TR/selectors-4/#specificity-rules | ||
function specificity(simpleSelector) { | ||
let A = 0; | ||
let B = 0; | ||
let C = 0; | ||
// A selector’s specificity is calculated for a given element as follows: | ||
simpleSelector.children.forEach((node) => { | ||
switch (node.type) { | ||
case 'SelectorList': | ||
case 'Selector': | ||
node.children.each(walk); | ||
break; | ||
// count the number of ID selectors in the selector (= A) | ||
case 'IdSelector': | ||
@@ -17,2 +43,3 @@ A++; | ||
// count the number of class selectors, attributes selectors, ... | ||
case 'ClassSelector': | ||
@@ -23,8 +50,54 @@ case 'AttributeSelector': | ||
// ... and pseudo-classes in the selector (= B) | ||
case 'PseudoClassSelector': | ||
switch (node.name.toLowerCase()) { | ||
// The specificity of an :is(), :not(), or :has() pseudo-class is replaced | ||
// by the specificity of the most specific complex selector in its selector list argument. | ||
case 'not': | ||
node.children.each(walk); | ||
case 'has': | ||
case 'is': | ||
// :matches() is used before it was renamed to :is() | ||
// https://github.com/w3c/csswg-drafts/issues/3258 | ||
case 'matches': | ||
// Older browsers support :is() functionality as prefixed pseudo-class :any() | ||
// https://developer.mozilla.org/en-US/docs/Web/CSS/:is | ||
case '-webkit-any': | ||
case '-moz-any': { | ||
const [a, b, c] = maxSelectorListSpecificity(node.children.first); | ||
A += a; | ||
B += b; | ||
C += c; | ||
break; | ||
} | ||
// Analogously, the specificity of an :nth-child() or :nth-last-child() selector | ||
// is the specificity of the pseudo class itself (counting as one pseudo-class selector) | ||
// plus the specificity of the most specific complex selector in its selector list argument (if any). | ||
case 'nth-child': | ||
case 'nth-last-child': { | ||
const arg = node.children.first; | ||
if (arg.type === 'Nth' && arg.selector) { | ||
const [a, b, c] = maxSelectorListSpecificity(arg.selector); | ||
A += a; | ||
B += b + 1; | ||
C += c; | ||
} else { | ||
B++; | ||
} | ||
break; | ||
} | ||
// The specificity of a :where() pseudo-class is replaced by zero. | ||
case 'where': | ||
break; | ||
// The four Level 2 pseudo-elements (::before, ::after, ::first-line, and ::first-letter) may, | ||
// for legacy reasons, be represented using the <pseudo-class-selector> grammar, | ||
// with only a single ":" character at their start. | ||
// https://www.w3.org/TR/selectors-4/#single-colon-pseudos | ||
case 'before': | ||
@@ -37,3 +110,2 @@ case 'after': | ||
// TODO: support for :nth-*(.. of <SelectorList>), :matches(), :has() | ||
default: | ||
@@ -44,9 +116,6 @@ B++; | ||
case 'PseudoElementSelector': | ||
C++; | ||
break; | ||
// count the number of type selectors ... | ||
case 'TypeSelector': | ||
// ignore universal selector | ||
if (node.name.charAt(node.name.length - 1) !== '*') { | ||
// ignore the universal selector | ||
if (!node.name.endsWith('*')) { | ||
C++; | ||
@@ -56,2 +125,6 @@ } | ||
// ... and pseudo-elements in the selector (= C) | ||
case 'PseudoElementSelector': | ||
C++; | ||
break; | ||
} | ||
@@ -62,1 +135,3 @@ }); | ||
}; | ||
export default specificity; |
@@ -1,6 +0,6 @@ | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
const { hasOwnProperty } = Object.prototype; | ||
function isEqualSelectors(a, b) { | ||
var cursor1 = a.head; | ||
var cursor2 = b.head; | ||
export function isEqualSelectors(a, b) { | ||
let cursor1 = a.head; | ||
let cursor2 = b.head; | ||
@@ -15,5 +15,5 @@ while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { | ||
function isEqualDeclarations(a, b) { | ||
var cursor1 = a.head; | ||
var cursor2 = b.head; | ||
export function isEqualDeclarations(a, b) { | ||
let cursor1 = a.head; | ||
let cursor2 = b.head; | ||
@@ -28,4 +28,4 @@ while (cursor1 !== null && cursor2 !== null && cursor1.data.id === cursor2.data.id) { | ||
function compareDeclarations(declarations1, declarations2) { | ||
var result = { | ||
export function compareDeclarations(declarations1, declarations2) { | ||
const result = { | ||
eq: [], | ||
@@ -37,11 +37,11 @@ ne1: [], | ||
var fingerprints = Object.create(null); | ||
var declarations2hash = Object.create(null); | ||
const fingerprints = Object.create(null); | ||
const declarations2hash = Object.create(null); | ||
for (var cursor = declarations2.head; cursor; cursor = cursor.next) { | ||
for (let cursor = declarations2.head; cursor; cursor = cursor.next) { | ||
declarations2hash[cursor.data.id] = true; | ||
} | ||
for (var cursor = declarations1.head; cursor; cursor = cursor.next) { | ||
var data = cursor.data; | ||
for (let cursor = declarations1.head; cursor; cursor = cursor.next) { | ||
const data = cursor.data; | ||
@@ -60,4 +60,4 @@ if (data.fingerprint) { | ||
for (var cursor = declarations2.head; cursor; cursor = cursor.next) { | ||
var data = cursor.data; | ||
for (let cursor = declarations2.head; cursor; cursor = cursor.next) { | ||
const data = cursor.data; | ||
@@ -79,9 +79,9 @@ if (declarations2hash[data.id]) { | ||
function addSelectors(dest, source) { | ||
source.each(function(sourceData) { | ||
var newStr = sourceData.id; | ||
var cursor = dest.head; | ||
export function addSelectors(dest, source) { | ||
source.forEach((sourceData) => { | ||
const newStr = sourceData.id; | ||
let cursor = dest.head; | ||
while (cursor) { | ||
var nextStr = cursor.data.id; | ||
const nextStr = cursor.data.id; | ||
@@ -106,7 +106,7 @@ if (nextStr === newStr) { | ||
// check if simpleselectors has no equal specificity and element selector | ||
function hasSimilarSelectors(selectors1, selectors2) { | ||
var cursor1 = selectors1.head; | ||
export function hasSimilarSelectors(selectors1, selectors2) { | ||
let cursor1 = selectors1.head; | ||
while (cursor1 !== null) { | ||
var cursor2 = selectors2.head; | ||
let cursor2 = selectors2.head; | ||
@@ -128,3 +128,3 @@ while (cursor2 !== null) { | ||
// test node can't to be skipped | ||
function unsafeToSkipNode(node) { | ||
export function unsafeToSkipNode(node) { | ||
switch (node.type) { | ||
@@ -150,10 +150,1 @@ case 'Rule': | ||
} | ||
module.exports = { | ||
isEqualSelectors: isEqualSelectors, | ||
isEqualDeclarations: isEqualDeclarations, | ||
compareDeclarations: compareDeclarations, | ||
addSelectors: addSelectors, | ||
hasSimilarSelectors: hasSimilarSelectors, | ||
unsafeToSkipNode: unsafeToSkipNode | ||
}; |
@@ -1,5 +0,5 @@ | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
const { hasOwnProperty } = Object.prototype; | ||
function buildMap(list, caseInsensitive) { | ||
var map = Object.create(null); | ||
const map = Object.create(null); | ||
@@ -10,5 +10,3 @@ if (!Array.isArray(list)) { | ||
for (var i = 0; i < list.length; i++) { | ||
var name = list[i]; | ||
for (let name of list) { | ||
if (caseInsensitive) { | ||
@@ -29,5 +27,5 @@ name = name.toLowerCase(); | ||
var tags = buildMap(data.tags, true); | ||
var ids = buildMap(data.ids); | ||
var classes = buildMap(data.classes); | ||
const tags = buildMap(data.tags, true); | ||
const ids = buildMap(data.ids); | ||
const classes = buildMap(data.classes); | ||
@@ -41,10 +39,10 @@ if (tags === null && | ||
return { | ||
tags: tags, | ||
ids: ids, | ||
classes: classes | ||
tags, | ||
ids, | ||
classes | ||
}; | ||
} | ||
function buildIndex(data) { | ||
var scopes = false; | ||
export function buildIndex(data) { | ||
let scopes = false; | ||
@@ -54,4 +52,4 @@ if (data.scopes && Array.isArray(data.scopes)) { | ||
for (var i = 0; i < data.scopes.length; i++) { | ||
var list = data.scopes[i]; | ||
for (let i = 0; i < data.scopes.length; i++) { | ||
const list = data.scopes[i]; | ||
@@ -62,7 +60,5 @@ if (!list || !Array.isArray(list)) { | ||
for (var j = 0; j < list.length; j++) { | ||
var name = list[j]; | ||
for (const name of list) { | ||
if (hasOwnProperty.call(scopes, name)) { | ||
throw new Error('Class can\'t be used for several scopes: ' + name); | ||
throw new Error(`Class can't be used for several scopes: ${name}`); | ||
} | ||
@@ -78,8 +74,4 @@ | ||
blacklist: buildList(data.blacklist), | ||
scopes: scopes | ||
scopes | ||
}; | ||
} | ||
module.exports = { | ||
buildIndex: buildIndex | ||
}; |
{ | ||
"name": "csso", | ||
"version": "4.2.0", | ||
"version": "5.0.0", | ||
"description": "CSS minifier with structural optimisations", | ||
"homepage": "https://github.com/css/csso", | ||
"author": "Sergey Kryzhanovsky <skryzhanovsky@ya.ru> (https://github.com/afelix)", | ||
@@ -14,7 +13,4 @@ "maintainers": [ | ||
], | ||
"repository": "css/csso", | ||
"license": "MIT", | ||
"repository": "css/csso", | ||
"bugs": { | ||
"url": "https://github.com/css/csso/issues" | ||
}, | ||
"keywords": [ | ||
@@ -29,39 +25,56 @@ "css", | ||
], | ||
"type": "module", | ||
"main": "./lib/index", | ||
"unpkg": "dist/csso.esm.js", | ||
"jsdelivr": "dist/csso.esm.js", | ||
"browser": { | ||
"./cjs/version.cjs": "./dist/version.js", | ||
"./lib/version.js": "./dist/version.js" | ||
}, | ||
"exports": { | ||
".": { | ||
"import": "./lib/index.js", | ||
"require": "./cjs/index.cjs" | ||
}, | ||
"./dist/*": "./dist/*.js", | ||
"./package.json": "./package.json" | ||
}, | ||
"scripts": { | ||
"test": "mocha --reporter dot", | ||
"lint": "eslint lib test", | ||
"test": "mocha test --reporter ${REPORTER:-progress}", | ||
"test:cjs": "mocha cjs-test --reporter ${REPORTER:-progress}", | ||
"test:dist": "mocha dist/test --reporter ${REPORTER:-progress}", | ||
"lint": "eslint lib scripts test", | ||
"lint-and-test": "npm run lint && npm test", | ||
"build": "rollup --config && terser dist/csso.js --compress --mangle -o dist/csso.min.js", | ||
"coverage": "nyc npm test", | ||
"coveralls": "nyc report --reporter=text-lcov | coveralls", | ||
"travis": "nyc npm run lint-and-test && npm run coveralls", | ||
"hydrogen": "node --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces --redirect-code-traces-to=code.asm --trace_hydrogen_file=code.cfg --print-opt-code bin/csso --stat -o /dev/null", | ||
"prepublishOnly": "npm run build" | ||
"build": "npm run bundle && npm run esm-to-cjs", | ||
"build-and-test": "npm run build && npm run test:dist && npm run test:cjs", | ||
"bundle": "node scripts/bundle", | ||
"bundle-and-test": "npm run bundle && npm run test:dist", | ||
"esm-to-cjs": "node scripts/esm-to-cjs", | ||
"esm-to-cjs-and-test": "npm run esm-to-cjs && npm run test:cjs", | ||
"coverage": "c8 --reporter=lcovonly npm test", | ||
"prepublishOnly": "npm run lint-and-test && npm run build-and-test", | ||
"hydrogen": "node --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces --redirect-code-traces-to=code.asm --trace_hydrogen_file=code.cfg --print-opt-code bin/csso --stat -o /dev/null" | ||
}, | ||
"dependencies": { | ||
"css-tree": "^1.1.2" | ||
"css-tree": "2.0.1" | ||
}, | ||
"browser": { | ||
"css-tree": "css-tree/dist/csstree.min.js" | ||
}, | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^11.0.1", | ||
"@rollup/plugin-json": "^4.0.1", | ||
"@rollup/plugin-node-resolve": "^7.0.0", | ||
"coveralls": "^3.0.11", | ||
"eslint": "^6.8.0", | ||
"mocha": "^7.1.1", | ||
"nyc": "^15.0.0", | ||
"rollup": "^1.29.0", | ||
"source-map": "^0.6.1", | ||
"terser": "^4.6.3" | ||
"c8": "^7.10.0", | ||
"coveralls": "^3.1.1", | ||
"esbuild": "^0.14.1", | ||
"eslint": "^7.24.0", | ||
"mocha": "^9.1.2", | ||
"rollup": "^2.60.2", | ||
"source-map": "^0.6.1" | ||
}, | ||
"engines": { | ||
"node": ">=8.0.0" | ||
"node": "^12.20.0 || ^14.13.0 || >=15.0.0", | ||
"npm": ">=7.0.0" | ||
}, | ||
"files": [ | ||
"dist", | ||
"!dist/test", | ||
"cjs", | ||
"lib" | ||
] | ||
} |
133
README.md
[![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso) | ||
[![Build Status](https://travis-ci.org/css/csso.svg?branch=master)](https://travis-ci.org/css/csso) | ||
[![Build Status](https://github.com/css/csso/actions/workflows/build.yml/badge.svg)](https://github.com/css/csso/actions/workflows/build.yml) | ||
[![Coverage Status](https://coveralls.io/repos/github/css/csso/badge.svg?branch=master)](https://coveralls.io/github/css/csso?branch=master) | ||
@@ -7,9 +7,73 @@ [![NPM Downloads](https://img.shields.io/npm/dm/csso.svg)](https://www.npmjs.com/package/csso) | ||
CSSO (CSS Optimizer) is a CSS minifier. It performs three sort of transformations: cleaning (removing redundant), compression (replacement for shorter form) and restructuring (merge of declarations, rulesets and so on). As a result your CSS becomes much smaller. | ||
CSSO (CSS Optimizer) is a CSS minifier. It performs three sort of transformations: cleaning (removing redundants), compression (replacement for the shorter forms) and restructuring (merge of declarations, rules and so on). As a result an output CSS becomes much smaller in size. | ||
[![Originated by Yandex](https://cdn.rawgit.com/css/csso/8d1b89211ac425909f735e7d5df87ee16c2feec6/docs/yandex.svg)](https://www.yandex.com/) | ||
[![Sponsored by Avito](https://cdn.rawgit.com/css/csso/8d1b89211ac425909f735e7d5df87ee16c2feec6/docs/avito.svg)](https://www.avito.ru/) | ||
## Install | ||
## Ready to use | ||
``` | ||
npm install csso | ||
``` | ||
## Usage | ||
```js | ||
import { minify } from 'csso'; | ||
// CommonJS is also supported | ||
// const { minify } = require('csso'); | ||
const minifiedCss = minify('.test { color: #ff0000; }').css; | ||
console.log(minifiedCss); | ||
// .test{color:red} | ||
``` | ||
Bundles are also available for use in a browser: | ||
- `dist/csso.js` – minified IIFE with `csso` as global | ||
```html | ||
<script src="node_modules/csso/dist/csso.js"></script> | ||
<script> | ||
csso.minify('.example { color: green }'); | ||
</script> | ||
``` | ||
- `dist/csso.esm.js` – minified ES module | ||
```html | ||
<script type="module"> | ||
import { minify } from 'node_modules/csso/dist/csso.esm.js' | ||
minify('.example { color: green }'); | ||
</script> | ||
``` | ||
One of CDN services like `unpkg` or `jsDelivr` can be used. By default (for short path) a ESM version is exposing. For IIFE version a full path to a bundle should be specified: | ||
```html | ||
<!-- ESM --> | ||
<script type="module"> | ||
import * as csstree from 'https://cdn.jsdelivr.net/npm/csso'; | ||
import * as csstree from 'https://unpkg.com/csso'; | ||
</script> | ||
<!-- IIFE with an export to global --> | ||
<script src="https://cdn.jsdelivr.net/npm/csso/dist/csso.js"></script> | ||
<script src="https://unpkg.com/csso/dist/csso.js"></script> | ||
``` | ||
CSSO is based on [CSSTree](https://github.com/csstree/csstree) to parse CSS into AST, AST traversal and to generate AST back to CSS. All `CSSTree` API is available behind `syntax` field extended with `compress()` method. You may minify CSS step by step: | ||
```js | ||
import { syntax } from 'csso'; | ||
const ast = syntax.parse('.test { color: #ff0000; }'); | ||
const compressedAst = syntax.compress(ast).ast; | ||
const minifiedCss = syntax.generate(compressedAst); | ||
console.log(minifiedCss); | ||
// .test{color:red} | ||
``` | ||
> Warning: CSSO doesn't guarantee API behind a `syntax` field as well as AST format. Both might be changed with changes in CSSTree. If you rely heavily on `syntax` API, a better option might be to use CSSTree directly. | ||
## Related projects | ||
- [Web interface](http://css.github.io/csso/csso.html) | ||
@@ -24,9 +88,6 @@ - [csso-cli](https://github.com/css/csso-cli) – command line interface | ||
- [CSSO Visual Studio Code plugin](https://marketplace.visualstudio.com/items?itemName=Aneryu.csso) | ||
- [vscode-csso](https://github.com/1000ch/vscode-csso) - Visual Studio Code plugin | ||
- [atom-csso](https://github.com/1000ch/atom-csso) - Atom plugin | ||
- [Sublime-csso](https://github.com/1000ch/Sublime-csso) - Sublime plugin | ||
## Install | ||
``` | ||
npm install csso | ||
``` | ||
## API | ||
@@ -47,27 +108,2 @@ | ||
Basic usage: | ||
```js | ||
var csso = require('csso'); | ||
var minifiedCss = csso.minify('.test { color: #ff0000; }').css; | ||
console.log(minifiedCss); | ||
// .test{color:red} | ||
``` | ||
CSSO is based on [CSSTree](https://github.com/csstree/csstree) to parse CSS into AST, AST traversal and to generate AST back to CSS. All `CSSTree` API is available behind `syntax` field. You may minify CSS step by step: | ||
```js | ||
var csso = require('csso'); | ||
var ast = csso.syntax.parse('.test { color: #ff0000; }'); | ||
var compressedAst = csso.syntax.compress(ast).ast; | ||
var minifiedCss = csso.syntax.generate(compressedAst); | ||
console.log(minifiedCss); | ||
// .test{color:red} | ||
``` | ||
> Warning: CSSO uses early versions of CSSTree that still in active development. CSSO doesn't guarantee API behind `syntax` field or AST format will not change in future releases of CSSO, since it's subject to change in CSSTree. Be careful with CSSO updates if you use `syntax` API until this warning removal. | ||
### minify(source[, options]) | ||
@@ -78,3 +114,3 @@ | ||
```js | ||
var result = csso.minify('.test { color: #ff0000; }', { | ||
const result = csso.minify('.test { color: #ff0000; }', { | ||
restructure: false, // don't change CSS structure, i.e. don't merge declarations, rulesets etc | ||
@@ -138,3 +174,3 @@ debug: true // show additional debug information: | ||
```js | ||
var result = csso.minifyBlock('color: rgba(255, 0, 0, 1); color: #ff0000'); | ||
const result = csso.minifyBlock('color: rgba(255, 0, 0, 1); color: #ff0000'); | ||
@@ -208,5 +244,5 @@ console.log(result.css); | ||
```js | ||
var csso = require('csso'); | ||
var css = fs.readFileSync('path/to/my.css', 'utf8'); | ||
var result = csso.minify(css, { | ||
const csso = require('csso'); | ||
const css = fs.readFileSync('path/to/my.css', 'utf8'); | ||
const result = csso.minify(css, { | ||
filename: 'path/to/my.css', // will be added to source map as reference to source file | ||
@@ -226,8 +262,9 @@ sourceMap: true // generate source map | ||
```js | ||
var require('source-map'); | ||
var csso = require('csso'); | ||
var inputFile = 'path/to/my.css'; | ||
var input = fs.readFileSync(inputFile, 'utf8'); | ||
var inputMap = input.match(/\/\*# sourceMappingURL=(\S+)\s*\*\/\s*$/); | ||
var output = csso.minify(input, { | ||
import { SourceMapConsumer } from 'source-map'; | ||
import * as csso from 'csso'; | ||
const inputFile = 'path/to/my.css'; | ||
const input = fs.readFileSync(inputFile, 'utf8'); | ||
const inputMap = input.match(/\/\*# sourceMappingURL=(\S+)\s*\*\/\s*$/); | ||
const output = csso.minify(input, { | ||
filename: inputFile, | ||
@@ -249,3 +286,3 @@ sourceMap: true | ||
'/*# sourceMappingURL=data:application/json;base64,' + | ||
new Buffer(output.map.toString()).toString('base64') + | ||
Buffer.from(output.map.toString()).toString('base64') + | ||
' */' | ||
@@ -252,0 +289,0 @@ ); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
597030
7
90
7035
410
Yes
1
1
2
1
+ Addedcss-tree@2.0.1(transitive)
+ Addedmdn-data@2.0.23(transitive)
- Removedcss-tree@1.1.3(transitive)
- Removedmdn-data@2.0.14(transitive)
Updatedcss-tree@2.0.1