diff options
Diffstat (limited to 'scripts/frontend/startup_css/clean_css.js')
-rw-r--r-- | scripts/frontend/startup_css/clean_css.js | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/scripts/frontend/startup_css/clean_css.js b/scripts/frontend/startup_css/clean_css.js new file mode 100644 index 00000000000..67a0453e816 --- /dev/null +++ b/scripts/frontend/startup_css/clean_css.js @@ -0,0 +1,83 @@ +const { memoize, isString, isRegExp } = require('lodash'); +const { parse } = require('postcss'); +const { CSS_TO_REMOVE } = require('./constants'); + +const getSelectorRemoveTesters = memoize(() => + CSS_TO_REMOVE.map((x) => { + if (isString(x)) { + return (selector) => x === selector; + } + if (isRegExp(x)) { + return (selector) => x.test(selector); + } + + throw new Error(`Unexpected type in CSS_TO_REMOVE content "${x}". Expected String or RegExp.`); + }), +); + +const getRemoveTesters = memoize(() => { + const selectorTesters = getSelectorRemoveTesters(); + + // These are mostly carried over from the previous project + // https://gitlab.com/gitlab-org/frontend/gitlab-css-statistics/-/blob/2aa00af25dba08fc71081c77206f45efe817ea4b/lib/gl_startup_extract.js + return [ + (node) => node.type === 'comment', + (node) => + node.type === 'atrule' && + (node.params === 'print' || + node.params === 'prefers-reduced-motion: reduce' || + node.name === 'keyframe' || + node.name === 'charset'), + (node) => node.selector && node.selectors && !node.selectors.length, + (node) => node.selector && selectorTesters.some((fn) => fn(node.selector)), + (node) => + node.type === 'decl' && + (node.prop === 'transition' || + node.prop.indexOf('-webkit-') > -1 || + node.prop.indexOf('-ms-') > -1), + ]; +}); + +const getNodesToRemove = (nodes) => { + const removeTesters = getRemoveTesters(); + const remNodes = []; + + nodes.forEach((node) => { + if (removeTesters.some((fn) => fn(node))) { + remNodes.push(node); + } else if (node.nodes?.length) { + remNodes.push(...getNodesToRemove(node.nodes)); + } + }); + + return remNodes; +}; + +const getEmptyNodesToRemove = (nodes) => + nodes + .filter((node) => node.nodes) + .reduce((acc, node) => { + if (node.nodes.length) { + acc.push(...getEmptyNodesToRemove(node.nodes)); + } else { + acc.push(node); + } + + return acc; + }, []); + +const cleanCSS = (css) => { + const cssRoot = parse(css); + + getNodesToRemove(cssRoot.nodes).forEach((node) => { + node.remove(); + }); + + getEmptyNodesToRemove(cssRoot.nodes).forEach((node) => { + node.remove(); + }); + + return cssRoot.toResult().css; +}; + +module.exports = { cleanCSS }; |