Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
const postcss = require("postcss");
// 行番号・列番号をソースコード上の位置(0 〜 ソースコード.length - 1)に変換
const lineColumnToIndex = (coverage, line, column) => {
// 各行の改行文字を含む文字数
const countPerLine = coverage.text.split("\n").map((lineStr, index, lines) =>
// 最終行以外は改行文字分の+1をする
index === lines.length - 1 ? lineStr.length : lineStr.length + 1
);
// 配列の要素を全て足す関数
const sum = arr => arr.reduce((acc, val) => acc + val, 0);
// 1~${line - 1}行まで文字数 + column = ソースコード上の位置
return sum(countPerLine.slice(0, line)) + column - 1;
};
// ノードが削除対象か判定
const isNodeUnneeded = (node, coverage) => {
// Root, Comment, Declarationは削除しない
if (["root", "comment", "decl"].includes(node.type)) {
return false;
}
// @font-faceは削除しない
if (node.type === "atrule" && node.name === "font-face") {
return false;
}
// その他:カバレッジが無ければ削除
// ノードのソース上での開始・終了位置(行列番号)
const { start, end } = node.source;
// 行列番号を文字列位置(0 ~ ソースコード文字列.length - 1)に変換
const startIndex = lineColumnToIndex(coverage, start.line, start.column);
const endIndex = lineColumnToIndex(coverage, end.line, end.column);
// ノードのソースコード上の範囲を含むカバレッジを探す
const covered = coverage.ranges.find(
range => !(startIndex => range.end || endIndex < range.start)
);
// カバレッジが見つかれなければ、trueを返す
return typeof covered === "undefined";
};
/**
* PuppeteerのCSSカバレッジから不要なスタイルを削除したCSSを生成
*
* @param {Object} coverage - stopCSSCoverageが返す配列の要素
*/
const removeUnusedCSS = coverage => {
// CSSからASTを生成
const root = postcss.parse(coverage.text);
// ASTの探索
root.walk(node => {
// 削除対象か?
if (isNodeUnneeded(node, coverage)) {
// 削除対象ならASTから削除する
node.remove();
}
});
// ASTからCSSを生成
return root.toString();
};
module.exports = removeUnusedCSS;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.