all files / postcss-scopify/ index.js

100% Statements 36/36
100% Branches 23/23
100% Functions 1/1
100% Lines 36/36
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                      39× 39×               12×   10×               44× 16×   28×                 12× 10×       12×     12×   12×       44× 13×     31× 39×       34× 12×     22×                  
'use strict';
 
const conditionalGroupRules = ['media','supports','document'];
 
const isObject = item => {
    return item != null && typeof item === 'object' && Array.isArray(item) === false;
}
 
/**
 * Determine if selector is already scoped
 *
 * @param {string} selector
 * @param {string} scope
 */
const isScopeApplied = (selector, scope) => {
    const selectorTopScope = selector.split(" ",1)[0];
    return selectorTopScope === scope;
}
 
/**
 * Determine if scope is valid
 *
 * @param {string} scope
 */
const isValidScope = scope => {
    if (!scope){
        return false;
    }
    return scope.indexOf(',') ===  -1;
}
 
/**
 * Determine if rule should be scoped
 *
 * @param {Rule} rule
 */
const isRuleScopable = rule => {
    if(rule.parent.type !== 'root') {
        return rule.parent.type === 'atrule' && conditionalGroupRules.indexOf(rule.parent.name) > -1;
    } else {
        return  true;
    }
}
 
/**
 * extract the scope from the input given by caller
 *
 * @param {string | Record<string, string>} options
 */
const extractScope = (options) => {
    if (typeof options === 'string') {
	    return options;
    } else if (isObject(options) && options.scope) {
            return options.scope
    }
    return null;
}
 
const scopify = (options) => {
    return {
        postcssPlugin: 'postcss-scopify',
        Once (root, { result }) {
            const scope = extractScope(options);
            // guard statment- allow only valid scopes
            if(!isValidScope(scope)){
                throw root.error('invalid scope', { plugin: 'postcss-scopify' });
            }
            root.walkRules(rule => {
 
                // skip scoping of special rules (certain At-rules, nested, etc')
                if(!isRuleScopable(rule)){
                    return rule;
                }
 
                rule.selectors = rule.selectors.map(selector => {
                    if (isScopeApplied(selector,scope)) {
                        return selector;
                    }
 
                    // special case for a top level '&' selector, resolves to scope
                    if (selector.includes('&')) {
                        return selector.replace(/&/g, scope);
                    }
 
                    return scope + ' ' + selector;
 
                });
            });
        },
    };
}
 
 
module.exports = scopify;
module.exports.postcss = true;