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
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
597030
7
90
7035
410
Yes
1
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