UNPKG

phtml-utility-class

Version:

Write utility classes conveniently while optimising your CSS

298 lines (245 loc) 7.94 kB
import phtml, { Element } from 'phtml'; import _ from 'lodash'; import { stripIndent } from 'common-tags'; import fs from 'fs-extra'; function genRegex(opts) { let tokens = {}; if (Object(opts).regex) { tokens = opts.regex; } else { tokens = { property: /[^-\s]+/, number: /[0-9]*\.?[0-9]+|\*/, unit: /px|cm|mm|in|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax/, seperator: /,/, arg: /0*({{number}})({{unit}})?|(\w+)/, args: /(?:({{arg}}){{seperator}}?)+/, decl: /({{property}})(?:-({{args}}))?/ }; } // Define what a token identifier looks like <word> or {{word}} let token = /{{(\w+)}}/gim; // Takes regex like /\d\w[0-9]<word>/ and replaces token identifier with matching token name function replaceTokenIdent(value, tokens) { return value.toString().replace(token, function (match, name) { if (tokens[name]) { if (tokens[name].toString().match(token)) { return replaceTokenIdent(tokens[name], tokens); } return tokens[name].source; } else { return match; } }); } // Go through each token in object and replace tokens identifier with value tokens = _.reduce(tokens, function (result, value, key) { return Object.assign({}, result, { [key]: replaceTokenIdent(value, tokens) }); }, {}); // Create new regex for each token return _.reduce(tokens, function (result, value, key) { return Object.assign({}, result, { [key]: new RegExp(value.replace(/\//g, ''), 'gmi') }); }, {}); } function getUtilities(str, re) { function findMatches(regex, str, matches = []) { const res = regex.exec(str); res && matches.push(res) && findMatches(regex, str, matches); return matches; } // var declRe = new RegExp(/\b([^-\s]+)(?:-((?:(0*([0-9]*\.?[0-9]+|\*)(px|cm|mm|in|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax)?|(\w+)),?)+))?\b/, 'gi') // let match = str.match(/\b([^-\s]+)(?:-((?:(0*([0-9]*\.?[0-9]+|\*)(px|cm|mm|in|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax)?|(\w+)),?)+))?\b/g); const matches = findMatches(re.decl, str); // console.log(match) let utilities = []; for (let i = 0; i < matches.length; i++) { let match = matches[i]; let utility = {}; if (match !== null) { utility.class = match[1]; // console.log(re.decl) utility.args = []; utility.decl = match[0]; if (match[2]) { /* Temporary fix for multiple arguments */ match[2].replace(new RegExp(re.arg, 'gmi'), function (arg) { if (arg === '*') arg = null; utility.args.push(arg); }); } if (utility.args.length === 0) { utility.args = null; } utilities.push(utility); } } return utilities; } var uniqid = require('uniqid'); const postcss = require('postcss'); const postcssrc = require('postcss-load-config'); // Get rules definitions var rules; if (fs.existsSync(process.cwd() + '/' + 'phtml-utility-class.config.js')) { rules = require(process.cwd() + '/' + 'phtml-utility-class.config.js').classes; // console.log(rules) } function putValuesIntoArray(value) { return Array.isArray(value) ? value : [value]; } function genStyles(utility, acc) { var styles = ''; if (utility.style({ rule: utility, args: utility.args, str: acc }) === undefined) { styles = ''; } else { styles = utility.style({ rule: utility, args: utility.args, str: acc }); } return `${styles}`; } async function processPostCSS(src, callback) { const ctx = { parser: true, map: 'inline' }; const { plugins, options } = postcssrc.sync(ctx); const { css } = await postcss(plugins).process(src, { from: undefined }); callback(css); } function processInlineStyles(node, classNameID) { const inlineStyles = node.attrs.get('style'); if (inlineStyles) { styles = ` .${classNameID}.${classNameID} {${inlineStyles}}`; processPostCSS(styles, css => { // Add new array back to element var styleTag = new Element({ name: 'style' }, null, css); // Add new array back to element var spanTag = new Element({ name: 'span' }, null, styleTag); spanTag.attrs.add({ style: 'display: none' }); spanTag.attrs.add({ class: 'stylup-sb' }); node.before(spanTag); node.attrs.remove('style'); var classNames = node.attrs.get('class') ? node.attrs.get('class').split(' ') : []; classNames.push(classNameID); node.attrs.add({ class: classNames.join(' ') }); }); } } var index = new phtml.Plugin('phtml-utility-class', opts => { opts = opts || {}; return { Element(node) { if (opts.processBlockStyles) { if (node.name === "style") { const target = node.nodes[0]; const source = target.data; processPostCSS(source, css => { node.innerHTML = css; }); } } var classNameID = uniqid(); if (process.env.NODE_ENV === "test") { classNameID = 'uniqid'; } // Get styles from style attr processInlineStyles(node, classNameID); const hasClass = node.attrs.get('class'); if (hasClass) { const classNames = hasClass ? node.attrs.get('class').split(' ') : null; var re = genRegex(opts); var utilities = getUtilities(hasClass, re); let newClassNames = [...classNames]; let styles = []; let hasUtilities = false; for (let utility of utilities) { // if (utilityClass) { // console.log('utility class') // } // else { // console.log('not utilit class') // } for (let rule of rules) { rule.class = putValuesIntoArray(rule.class); for (let property of rule.class) { var tempRule = Object.assign({}, rule); tempRule.class = property; if (utility.class === tempRule.class) { tempRule = Object.assign(tempRule, utility); hasUtilities = true; var output = ""; function acc(strings, ...values) { if (!strings) { if (typeof output !== "undefined") { return output = output.replace(/\n$/, ''); } else { return str; } } else { let str = ''; strings.forEach((string, a) => { str += string + (values[a] || ''); }); str = stripIndent(str); if (typeof output !== "undefined") { output += `${str}\n`; } return str; } } styles.push(genStyles(tempRule, acc)); newClassNames.push(utility.class); } } } } if (hasUtilities) { styles = ` .${classNameID}.${classNameID} { ${styles.join('')} }`; processPostCSS(styles, css => { // Add new array back to element var styleTag = new Element({ name: 'style' }, null, css); // Add new array back to element var spanTag = new Element({ name: 'span' }, null, styleTag); spanTag.attrs.add({ style: 'display: none' }); spanTag.attrs.add({ class: 'stylup-sb' }); node.before(spanTag); // Add classNameID newClassNames.push(classNameID); node.attrs.add({ class: newClassNames.join(' ') }); }); } } } }; }); export default index; //# sourceMappingURL=index.mjs.map