index.js 4.19 KB
'use strict';
const valueParser = require('postcss-value-parser');
const {
  normalizeGridAutoFlow,
  normalizeGridColumnRowGap,
  normalizeGridColumnRow,
} = require('./rules/grid');

// rules
const animation = require('./rules/animation');
const border = require('./rules/border');
const boxShadow = require('./rules/boxShadow');
const flexFlow = require('./rules/flexFlow');
const transition = require('./rules/transition');
const listStyle = require('./rules/listStyle');
const column = require('./rules/columns');
const vendorUnprefixed = require('./lib/vendorUnprefixed.js');

/** @type {[string, (parsed: valueParser.ParsedValue) => string][]} */
const borderRules = [
  ['border', border],
  ['border-block', border],
  ['border-inline', border],
  ['border-block-end', border],
  ['border-block-start', border],
  ['border-inline-end', border],
  ['border-inline-start', border],
  ['border-top', border],
  ['border-right', border],
  ['border-bottom', border],
  ['border-left', border],
];

/** @type {[string, (parsed: valueParser.ParsedValue) => string | string[] | valueParser.ParsedValue][]} */
const grid = [
  ['grid-auto-flow', normalizeGridAutoFlow],
  ['grid-column-gap', normalizeGridColumnRowGap], // normal | <length-percentage>
  ['grid-row-gap', normalizeGridColumnRowGap], // normal | <length-percentage>
  ['grid-column', normalizeGridColumnRow], // <grid-line>+
  ['grid-row', normalizeGridColumnRow], // <grid-line>+
  ['grid-row-start', normalizeGridColumnRow], // <grid-line>
  ['grid-row-end', normalizeGridColumnRow], // <grid-line>
  ['grid-column-start', normalizeGridColumnRow], // <grid-line>
  ['grid-column-end', normalizeGridColumnRow], // <grid-line>
];

/** @type {[string, (parsed: valueParser.ParsedValue) => string | valueParser.ParsedValue][]} */
const columnRules = [
  ['column-rule', border],
  ['columns', column],
];

/** @type {Map<string, ((parsed: valueParser.ParsedValue) => string | string[] | valueParser.ParsedValue)>} */
const rules = new Map([
  ['animation', animation],
  ['outline', border],
  ['box-shadow', boxShadow],
  ['flex-flow', flexFlow],
  ['list-style', listStyle],
  ['transition', transition],
  ...borderRules,
  ...grid,
  ...columnRules,
]);

const variableFunctions = new Set(['var', 'env', 'constant']);

/**
 * @param {valueParser.Node} node
 * @return {boolean}
 */
function isVariableFunctionNode(node) {
  if (node.type !== 'function') {
    return false;
  }

  return variableFunctions.has(node.value.toLowerCase());
}

/**
 * @param {valueParser.ParsedValue} parsed
 * @return {boolean}
 */
function shouldAbort(parsed) {
  let abort = false;

  parsed.walk((node) => {
    if (
      node.type === 'comment' ||
      isVariableFunctionNode(node) ||
      (node.type === 'word' && node.value.includes(`___CSS_LOADER_IMPORT___`))
    ) {
      abort = true;

      return false;
    }
  });

  return abort;
}

/**
 * @param {import('postcss').Declaration} decl
 * @return {string}
 */
function getValue(decl) {
  let { value, raws } = decl;

  if (raws && raws.value && raws.value.raw) {
    value = raws.value.raw;
  }

  return value;
}
/**
 * @type {import('postcss').PluginCreator<void>}
 * @return {import('postcss').Plugin}
 */
function pluginCreator() {
  return {
    postcssPlugin: 'postcss-ordered-values',
    prepare() {
      const cache = new Map();
      return {
        OnceExit(css) {
          css.walkDecls((decl) => {
            const lowerCasedProp = decl.prop.toLowerCase();
            const normalizedProp = vendorUnprefixed(lowerCasedProp);
            const processor = rules.get(normalizedProp);

            if (!processor) {
              return;
            }

            const value = getValue(decl);

            if (cache.has(value)) {
              decl.value = cache.get(value);

              return;
            }

            const parsed = valueParser(value);

            if (parsed.nodes.length < 2 || shouldAbort(parsed)) {
              cache.set(value, value);

              return;
            }

            const result = processor(parsed);

            decl.value = result.toString();
            cache.set(value, result.toString());
          });
        },
      };
    },
  };
}

pluginCreator.postcss = true;
module.exports = pluginCreator;