index.js 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. 'use strict';
  2. const OVERRIDABLE_RULES = new Set(['keyframes', 'counter-style']);
  3. const SCOPE_RULES = new Set(['media', 'supports']);
  4. /**
  5. * @param {string} prop
  6. * @return {string}
  7. */
  8. function vendorUnprefixed(prop) {
  9. return prop.replace(/^-\w+-/, '');
  10. }
  11. /**
  12. * @param {string} name
  13. * @return {boolean}
  14. */
  15. function isOverridable(name) {
  16. return OVERRIDABLE_RULES.has(vendorUnprefixed(name.toLowerCase()));
  17. }
  18. /**
  19. * @param {string} name
  20. * @return {boolean}
  21. */
  22. function isScope(name) {
  23. return SCOPE_RULES.has(vendorUnprefixed(name.toLowerCase()));
  24. }
  25. /**
  26. * @param {import('postcss').AtRule} node
  27. * @return {string}
  28. */
  29. function getScope(node) {
  30. /** @type {import('postcss').Container<import('postcss').ChildNode> | import('postcss').Document | undefined} */
  31. let current = node.parent;
  32. const chain = [node.name.toLowerCase(), node.params];
  33. while (current) {
  34. if (
  35. current.type === 'atrule' &&
  36. isScope(/** @type import('postcss').AtRule */ (current).name)
  37. ) {
  38. chain.unshift(
  39. /** @type import('postcss').AtRule */ (current).name +
  40. ' ' +
  41. /** @type import('postcss').AtRule */ (current).params
  42. );
  43. }
  44. current = current.parent;
  45. }
  46. return chain.join('|');
  47. }
  48. /**
  49. * @type {import('postcss').PluginCreator<void>}
  50. * @return {import('postcss').Plugin}
  51. */
  52. function pluginCreator() {
  53. return {
  54. postcssPlugin: 'postcss-discard-overridden',
  55. prepare() {
  56. const cache = new Map();
  57. /** @type {{node: import('postcss').AtRule, scope: string}[]} */
  58. const rules = [];
  59. return {
  60. OnceExit(css) {
  61. css.walkAtRules((node) => {
  62. if (isOverridable(node.name)) {
  63. const scope = getScope(node);
  64. cache.set(scope, node);
  65. rules.push({
  66. node,
  67. scope,
  68. });
  69. }
  70. });
  71. rules.forEach((rule) => {
  72. if (cache.get(rule.scope) !== rule.node) {
  73. rule.node.remove();
  74. }
  75. });
  76. },
  77. };
  78. },
  79. };
  80. }
  81. pluginCreator.postcss = true;
  82. module.exports = pluginCreator;