Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 56 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Dependencies
const postcss = require('postcss');
const deep = require('deep-get-set');
const kindOf = require('kind-of');

Expand All @@ -10,65 +9,71 @@ const defaults = {
data: {}
};

module.exports = postcss.plugin('postcss-replace', (opts = defaults) => {
function postCSSReplace(opts = defaults) {
const options = Object.assign({}, defaults, opts);

return (css) => {
// Check validity of provided pattern. If not valid throw TypeError
let regex = null;

// Check validity of provided pattern. If not valid throw TypeError
let regex = null;
if (options.pattern instanceof RegExp) {
regex = options.pattern;
} else if (typeof options.pattern === 'string') {
regex = new RegExp(options.pattern, 'gi');
} else {
throw new TypeError(`Invalid pattern provided. It is expected to be a string or an instance of RegExp. Got: ${kindOf(options.pattern)}`);
}

if (options.pattern instanceof RegExp) {
regex = options.pattern;
} else if (typeof options.pattern === 'string') {
regex = new RegExp(options.pattern, 'gi');
} else {
throw new TypeError(`Invalid pattern provided. It is expected to be a string or an instance of RegExp. Got: ${kindOf(options.pattern)}`);
const replacementArgs = options.data && options.data.replaceAll != null ? [regex, options.data.replaceAll] : [regex, (match, key) => {
const replace = deep(options.data, key);

if (typeof replace !== 'string') {
return match;
}

const replacementArgs = options.data && options.data.replaceAll != null ? [regex, options.data.replaceAll] : [regex, (match, key) => {
const replace = deep(options.data, key);
return replace;
}];

return {
postcssPlugin: 'postcss-replace',
OnceExit(root) {
root[options.commentsOnly ? 'walkComments' : 'walk']((node) => {

if (typeof replace !== 'string') {
return match;
}
// Before we had the switch statement, we just used node.replaceValues(). This could potentially lead to
// incorrect behaviour as described in https://github.com/gridonic/postcss-replace/issues/5.
//
// So for example if the CSS contains at-rules like @media, calling replaceValues() would replace
// everything inside the @media { … } statement and since we are walking through *all* nodes, we would
// encounter the nodes from the @media statement again in the next iteration/call of our walk function.
//
// This is why we have refactored the logic of the walk function to use a switch statement in order to do
// the replacement only on the relevant nodes and use the appropriate replacement logic.
//
// Furthermore it also makes adding/handling new cases quite comfortable.
//
// @see http://api.postcss.org/
switch (node.constructor.name) {
case 'Comment':
node.text = node.text.replace(...replacementArgs);
break;

return replace;
}];
case 'Declaration':
node.prop = node.prop.replace(...replacementArgs);
node.value = node.value.replace(...replacementArgs);
break;

css[options.commentsOnly ? 'walkComments' : 'walk']((node) => {
case 'AtRule':
node.params = node.params.replace(...replacementArgs);
break;

// Before we had the switch statement, we just used node.replaceValues(). This could potentially lead to
// incorrect behaviour as described in https://github.com/gridonic/postcss-replace/issues/5.
//
// So for example if the CSS contains at-rules like @media, calling replaceValues() would replace
// everything inside the @media { … } statement and since we are walking through *all* nodes, we would
// encounter the nodes from the @media statement again in the next iteration/call of our walk function.
//
// This is why we have refactored the logic of the walk function to use a switch statement in order to do
// the replacement only on the relevant nodes and use the appropriate replacement logic.
//
// Furthermore it also makes adding/handling new cases quite comfortable.
//
// @see http://api.postcss.org/
switch (node.constructor.name) {
case 'Comment':
node.text = node.text.replace(...replacementArgs);
break;
case 'Rule':
node.selector = node.selector.replace(...replacementArgs);
break;
}
});
}
}
}

case 'Declaration':
node.prop = node.prop.replace(...replacementArgs);
node.value = node.value.replace(...replacementArgs);
break;
postCSSReplace.postcss = true;

case 'AtRule':
node.params = node.params.replace(...replacementArgs);
break;

case 'Rule':
node.selector = node.selector.replace(...replacementArgs);
break;
}
});
};
});
module.exports = postCSSReplace;
Loading