123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- let featureQueries = require('caniuse-lite/data/features/css-featurequeries.js')
- let { feature } = require('caniuse-lite')
- let { parse } = require('postcss')
- let Browsers = require('./browsers')
- let brackets = require('./brackets')
- let Value = require('./value')
- let utils = require('./utils')
- let data = feature(featureQueries)
- let supported = []
- for (let browser in data.stats) {
- let versions = data.stats[browser]
- for (let version in versions) {
- let support = versions[version]
- if (/y/.test(support)) {
- supported.push(browser + ' ' + version)
- }
- }
- }
- class Supports {
- constructor(Prefixes, all) {
- this.Prefixes = Prefixes
- this.all = all
- }
- /**
- * Return prefixer only with @supports supported browsers
- */
- prefixer() {
- if (this.prefixerCache) {
- return this.prefixerCache
- }
- let filtered = this.all.browsers.selected.filter(i => {
- return supported.includes(i)
- })
- let browsers = new Browsers(
- this.all.browsers.data,
- filtered,
- this.all.options
- )
- this.prefixerCache = new this.Prefixes(
- this.all.data,
- browsers,
- this.all.options
- )
- return this.prefixerCache
- }
- /**
- * Parse string into declaration property and value
- */
- parse(str) {
- let parts = str.split(':')
- let prop = parts[0]
- let value = parts[1]
- if (!value) value = ''
- return [prop.trim(), value.trim()]
- }
- /**
- * Create virtual rule to process it by prefixer
- */
- virtual(str) {
- let [prop, value] = this.parse(str)
- let rule = parse('a{}').first
- rule.append({ prop, value, raws: { before: '' } })
- return rule
- }
- /**
- * Return array of Declaration with all necessary prefixes
- */
- prefixed(str) {
- let rule = this.virtual(str)
- if (this.disabled(rule.first)) {
- return rule.nodes
- }
- let result = { warn: () => null }
- let prefixer = this.prefixer().add[rule.first.prop]
- prefixer && prefixer.process && prefixer.process(rule.first, result)
- for (let decl of rule.nodes) {
- for (let value of this.prefixer().values('add', rule.first.prop)) {
- value.process(decl)
- }
- Value.save(this.all, decl)
- }
- return rule.nodes
- }
- /**
- * Return true if brackets node is "not" word
- */
- isNot(node) {
- return typeof node === 'string' && /not\s*/i.test(node)
- }
- /**
- * Return true if brackets node is "or" word
- */
- isOr(node) {
- return typeof node === 'string' && /\s*or\s*/i.test(node)
- }
- /**
- * Return true if brackets node is (prop: value)
- */
- isProp(node) {
- return (
- typeof node === 'object' &&
- node.length === 1 &&
- typeof node[0] === 'string'
- )
- }
- /**
- * Return true if prefixed property has no unprefixed
- */
- isHack(all, unprefixed) {
- let check = new RegExp(`(\\(|\\s)${utils.escapeRegexp(unprefixed)}:`)
- return !check.test(all)
- }
- /**
- * Return true if we need to remove node
- */
- toRemove(str, all) {
- let [prop, value] = this.parse(str)
- let unprefixed = this.all.unprefixed(prop)
- let cleaner = this.all.cleaner()
- if (
- cleaner.remove[prop] &&
- cleaner.remove[prop].remove &&
- !this.isHack(all, unprefixed)
- ) {
- return true
- }
- for (let checker of cleaner.values('remove', unprefixed)) {
- if (checker.check(value)) {
- return true
- }
- }
- return false
- }
- /**
- * Remove all unnecessary prefixes
- */
- remove(nodes, all) {
- let i = 0
- while (i < nodes.length) {
- if (
- !this.isNot(nodes[i - 1]) &&
- this.isProp(nodes[i]) &&
- this.isOr(nodes[i + 1])
- ) {
- if (this.toRemove(nodes[i][0], all)) {
- nodes.splice(i, 2)
- continue
- }
- i += 2
- continue
- }
- if (typeof nodes[i] === 'object') {
- nodes[i] = this.remove(nodes[i], all)
- }
- i += 1
- }
- return nodes
- }
- /**
- * Clean brackets with one child
- */
- cleanBrackets(nodes) {
- return nodes.map(i => {
- if (typeof i !== 'object') {
- return i
- }
- if (i.length === 1 && typeof i[0] === 'object') {
- return this.cleanBrackets(i[0])
- }
- return this.cleanBrackets(i)
- })
- }
- /**
- * Add " or " between properties and convert it to brackets format
- */
- convert(progress) {
- let result = ['']
- for (let i of progress) {
- result.push([`${i.prop}: ${i.value}`])
- result.push(' or ')
- }
- result[result.length - 1] = ''
- return result
- }
- /**
- * Compress value functions into a string nodes
- */
- normalize(nodes) {
- if (typeof nodes !== 'object') {
- return nodes
- }
- nodes = nodes.filter(i => i !== '')
- if (typeof nodes[0] === 'string') {
- let firstNode = nodes[0].trim()
- if (
- firstNode.includes(':') ||
- firstNode === 'selector' ||
- firstNode === 'not selector'
- ) {
- return [brackets.stringify(nodes)]
- }
- }
- return nodes.map(i => this.normalize(i))
- }
- /**
- * Add prefixes
- */
- add(nodes, all) {
- return nodes.map(i => {
- if (this.isProp(i)) {
- let prefixed = this.prefixed(i[0])
- if (prefixed.length > 1) {
- return this.convert(prefixed)
- }
- return i
- }
- if (typeof i === 'object') {
- return this.add(i, all)
- }
- return i
- })
- }
- /**
- * Add prefixed declaration
- */
- process(rule) {
- let ast = brackets.parse(rule.params)
- ast = this.normalize(ast)
- ast = this.remove(ast, rule.params)
- ast = this.add(ast, rule.params)
- ast = this.cleanBrackets(ast)
- rule.params = brackets.stringify(ast)
- }
- /**
- * Check global options
- */
- disabled(node) {
- if (!this.all.options.grid) {
- if (node.prop === 'display' && node.value.includes('grid')) {
- return true
- }
- if (node.prop.includes('grid') || node.prop === 'justify-items') {
- return true
- }
- }
- if (this.all.options.flexbox === false) {
- if (node.prop === 'display' && node.value.includes('flex')) {
- return true
- }
- let other = ['order', 'justify-content', 'align-items', 'align-content']
- if (node.prop.includes('flex') || other.includes(node.prop)) {
- return true
- }
- }
- return false
- }
- }
- module.exports = Supports
|