-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
102 lines (83 loc) · 3.42 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
'use strict';
var postcss = require('postcss');
var objectAssign = require('object-assign');
var defaults = {
unitToConvert: 'px',
viewportWidth: 320,
viewportHeight: 568, // not now used; TODO: need for different units and math for different properties
unitPrecision: 5,
viewportUnit: 'vw',
fontViewportUnit: 'vw', // vmin is more suitable.
selectorBlackList: [],
minPixelValue: 1,
mediaQuery: false,
exclude: [] // ignore some files
};
module.exports = postcss.plugin('postcss-px-to-viewport', function (options) {
var opts = objectAssign({}, defaults, options);
var pxReplace = createPxReplace(opts.viewportWidth, opts.minPixelValue, opts.unitPrecision, opts.viewportUnit);
// excluding regex trick: http://www.rexegg.com/regex-best-trick.html
// Not anything inside double quotes
// Not anything inside single quotes
// Not anything inside url()
// Any digit followed by px
// !singlequotes|!doublequotes|!url()|pixelunit
var pxRegex = new RegExp('"[^"]+"|\'[^\']+\'|url\\([^\\)]+\\)|(\\d*\\.?\\d+)' + opts.unitToConvert, 'ig')
return function (css) {
css.walkDecls(function (decl, i) {
// Add exlclude option to ignore some files like 'node_modules'
if (opts.exclude) {
if (Object.prototype.toString.call(opts.exclude) === '[object RegExp]') {
if (!handleExclude(opts.exclude, decl.source.input.file)) return;
} else if (Object.prototype.toString.call(opts.exclude) === '[object Array]') {
for (let i = 0; i < opts.exclude.length; ++i) {
if (!handleExclude(opts.exclude[i], decl.source.input.file)) return;
}
} else {
throw new Error('options.exclude should be RegExp or Array!');
}
}
// This should be the fastest test and will remove most declarations
if (decl.value.indexOf(opts.unitToConvert) === -1) return;
if (blacklistedSelector(opts.selectorBlackList, decl.parent.selector)) return;
var unit = getUnit(decl.prop, opts)
decl.value = decl.value.replace(pxRegex, createPxReplace(opts.viewportWidth, opts.minPixelValue, opts.unitPrecision, unit));
});
if (opts.mediaQuery) {
css.walkAtRules('media', function (rule) {
if (rule.params.indexOf(opts.unitToConvert) === -1) return;
rule.params = rule.params.replace(pxRegex, pxReplace);
});
}
};
});
function handleExclude (reg, file) {
if (Object.prototype.toString.call(reg) !== '[object RegExp]') {
throw new Error('options.exclude should be RegExp!');
}
if (file.match(reg) !== null) return false;
return true;
}
function getUnit(prop, opts) {
return prop.indexOf('font') === -1 ? opts.viewportUnit : opts.fontViewportUnit;
}
function createPxReplace(viewportSize, minPixelValue, unitPrecision, viewportUnit) {
return function (m, $1) {
if (!$1) return m;
var pixels = parseFloat($1);
if (pixels <= minPixelValue) return m;
return toFixed((pixels / viewportSize * 100), unitPrecision) + viewportUnit;
};
}
function toFixed(number, precision) {
var multiplier = Math.pow(10, precision + 1),
wholeNumber = Math.floor(number * multiplier);
return Math.round(wholeNumber / 10) * 10 / multiplier;
}
function blacklistedSelector(blacklist, selector) {
if (typeof selector !== 'string') return;
return blacklist.some(function (regex) {
if (typeof regex === 'string') return selector.indexOf(regex) !== -1;
return selector.match(regex);
});
}