packages/postcss-aspect-ratio-from-bg-image/index.js (65 lines of code) (raw):

const { dirname } = require('path'); const merge = require('merge-options'); const postcss = require('postcss'); const calipers = require('calipers'); const { createMatcher, resolveFile } = require('svg-mixer-utils'); const { findBgDecls, objectToDeclProps, transformSelector } = require('svg-mixer-utils/lib/postcss'); const { name: packageName } = require('./package.json'); const { measure } = calipers('png', 'jpeg', 'svg'); function getAspectRatio(imagePath) { return measure(imagePath).then(data => { const { width, height } = data.pages[0]; return height / width; }); } const UNMATCHED_FILE_ERROR_CODE = 'UNMATCHED_FILE'; const defaultConfig = { selector: '::before', match: /\.(jpe?g|png|svg)(\?.*)?$/ }; module.exports = postcss.plugin(packageName, options => { const { match, selector } = merge(defaultConfig, options); const matcher = createMatcher(match); return root => { const from = root.source.input.file || process.cwd(); const decls = findBgDecls(root); const promises = decls.map(({ decl, helper }) => { const query = helper.URIS[0].search(); const url = helper.URIS[0].toString(); const rule = decl.parent; return resolveFile(url, dirname(from)) .then(absPath => (!matcher(absPath + query) ? Promise.reject({ code: UNMATCHED_FILE_ERROR_CODE }) : absPath) ) .then(getAspectRatio) .then(ratio => { const ratioFormatted = ratio % 1 !== 0 ? parseFloat(parseFloat(ratio).toFixed(2)) : Math.round(ratio); const percentage = ratioFormatted * 100; rule.cloneAfter({ selector: transformSelector(rule.selector, s => `${s}${selector}`) }) .removeAll() .append(...objectToDeclProps({ display: 'block', 'box-sizing': 'content-box', 'padding-bottom': `${percentage}%`, content: '\'\'' })); }) .catch(e => { let error = e; if (e.code === UNMATCHED_FILE_ERROR_CODE) { return; } else if (e.code === 'NOT_FOUND') { const msg = `${url} not found`; error = decl.error(msg, { word: decl.prop }); } // eslint-disable-next-line consistent-return return Promise.reject(error); }); }); return Promise.all(promises); }; });