空投控制系统
一个 特别快彻底而微小(~10 KB 分钟(未使用的CSS清洁剂) (麻省理工学院许可)
导言
DropCSS 将您的 HTML 和 CSS 作为输入, 只返回使用的 CSS 作为输出 。 它的自定义 HTML 和 CSS 解析器高度优化了99%的使用大小写,从而避免了处理错误的标记或样式表的间接费用,因此需要很好的输入. 对复杂的逃逸规则的处理很少,因此总是会有无法由DropCSS处理的有效输入的情况;对于这些不常见的情况,请 开始讨论。当 HTML 特性允许时 html, (中文(简体) ), head, (中文(简体) ), body 财务报告和审定财务报表 tbody dropCSS 将不作这样的假设; 选择器将只保留给可从提供的标记中解析的标记.
通过一个结构优化器来运行你的CSS也是个好主意 干净, (中文(简体) ), 顺位, (中文(简体) ), 克斯纳诺 或者说 疯子 以重新分组选择者,合并冗余规则等。 在DropCSS之后这样做也许有道理,因为DropCSS会留下多余的块,例如. .foo, .bar { color: red; } .bar { width: 50%; } - > (中文) .bar { color: red; } .bar { width: 50%; } 若为 .foo 正在离开你的标记.
更多关于这个项目的背面故事和讨论:v0.1.0 alpha: /r/javascript, (中文(简体) ), 黑客新闻 和 v1.0.0 发布 : /r/javascript。 。 。 .
现场德莫: https://codepen.io/leeoniya/pen/LvbRyq 页面存档备份,存于互联网档案馆
安装
npm install -D dropcss
用法 & API
const dropcss = require('dropcss');
let html = `
<html>
<head></head>
<body>
<p>Hello World!</p>
</body>
</html>
`;
let css = `
.card {
padding: 8px;
}
p:hover a:first-child {
color: red;
}
`;
const whitelist = /#foo|\.bar/;
let dropped = new Set();
let cleaned = dropcss({
html,
css,
shouldDrop: (sel) => {
if (whitelist.test(sel))
return false;
else {
dropped.add(sel);
return true;
}
},
});
console.log(cleaned.css);
console.log(dropped);
这个 shouldDrop 钩对无法匹配的 CSS 选择器 html返回时 false 以保留选择器或 true 放弃它.
特征
-
支持的选择者
常见 属性 立场 位置(类型) 其它 *- 普遍性<tag>- 标签#- 编号.- 类- 后裔>- 儿童+- 相邻的兄弟~- 将军兄弟[attr][attr=val][attr*=val][attr^=val][attr$=val][attr~=val]:first-child:last-child:only-child:nth-child():nth-last-child():first-of-type:last-of-type:only-of-type:nth-of-type():nth-last-of-type():not():is():has() -
保留所有无法从解析的 HTML 中决定性地检查的瞬态伪类和伪元素选择器 .
-
清除未使用
@font-face财务报告和审定财务报表@keyframes块. -
清除未使用 CSS 变量。 。 。 .
-
深入解析综合CSS变量,例如:
:root { --font-style: italic; --font-weight: bold; --line-height: var(--height)em; --font-family: 'Open Sans'; --font: var(--font-style) var(--font-weight) 1em/var(--line-height) var(--font-family); --height: 1.6; } @font-face { font-family: var(--font-family); src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"), url("/fonts/OpenSans-Regular-webfont.woff") format("woff"); } body { font: var(--font); }
业绩
投入
测试.html
- 18.8 KB 改进
- 502个圆顶节点通过
document.querySelectorAll("*").length
样式.min.css
产出
| 自由大小w/ deps | 输出大小 | 减少 | 时间过后 | 未使用的字节( test.html 覆盖) | |
|---|---|---|---|---|---|
| 调试CSS |
58.4 KB (韩语) 6个文件, 2个文件夹 |
6.58 KB (韩语) | 76.15% | 21分钟 | 575个/ 8.5% |
| 失败 |
13.5 甲基溴 2 829个文件, 301个文件夹 |
6.72 KB (韩语) | 75.71% | 385分钟 | 638%/9.3% |
| 清洗器 |
2.69 甲基溴 560个文件, 119个文件夹 |
8.01 KB (韩语) | 71.05% (中文(简体) ) | 88分钟 | 1 806个/ 22.0% |
| 净化CSS |
3.46 甲基溴 792个文件, 207个文件夹 |
15.46 KB (韩语) | 44.34% | 173分钟 | 9,440人/59.6% 妇女 |
页:1
- 大约400个"未被使用的字节"是由于一个明确的/共享的白名单,而不是由于工具无法检测/移动CSS.
- 大约175个"未被使用的字节"是由于销售商前置(-moz,-ms)属性和选择器在Chrome中不活动,用于测试覆盖.
- 清洗不支持属性或复杂选择器 : 问题110。 。 。 .
满 压力测试 还提供.
Java 脚本执行
DropCSS 不装入外部资源或执行 <script> 标记,所以您的 HTML 必须完全形成( 或 SSR' d) 。 或者,你可以使用 傀儡 和本地的 http 服务器以获得充电 <script> 执行.
这是35行脚本 具体是:
const httpServer = require('http-server');
const puppeteer = require('puppeteer');
const fetch = require('node-fetch');
const dropcss = require('dropcss');
const server = httpServer.createServer({root: './www'});
server.listen(8080);
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://127.0.0.1:8080/index.html');
const html = await page.content();
const styleHrefs = await page.$$eval('link[rel=stylesheet]', els => Array.from(els).map(s => s.href));
await browser.close();
await Promise.all(styleHrefs.map(href =>
fetch(href).then(r => r.text()).then(css => {
let start = +new Date();
let clean = dropcss({
css,
html,
});
console.log({
stylesheet: href,
cleanCss: clean.css,
elapsed: +new Date() - start,
});
})
));
server.close();
})();
累积白名单
或许您想要取出一个巨大的 CSS 文件, 并针对多个 HTML 源对其进行清除, 从而保留 HTML 源中出现的任何选择器 。 在使用 Puppeteer 来引用不同的应用程序状态时,这也适用,以确保DropCSS在清理 CSS 之前将每个状态都考虑在内. 这个想法相当简单:
- 运行 DropCSS 以对抗 HTML 源 .
- 从每个结果中累积出一个白名单.
- 在空的 HTML 字符串下运行 DroppCSS, 只依靠累积的白名单 .
见 /减少/累积.js数字 :
const dropcss = require('dropcss');
// super mega-huge combined stylesheet
let css = `
em {
color: red;
}
p {
font-weight: bold;
}
.foo {
font-size: 10pt;
}
`;
// html of page (or state) A
let htmlA = `
<html>
<head></head>
<body>
<em>Hello World!</em>
</body>
</html>
`;
// html of page (or state) B
let htmlB = `
<html>
<head></head>
<body>
<p>Soft Kitties!</p>
</body>
</html>
`;
// whitelist
let whitelist = new Set();
function didRetain(sel) {
whitelist.add(sel);
}
let resA = dropcss({
css,
html: htmlA,
didRetain,
});
let resB = dropcss({
css,
html: htmlB,
didRetain,
});
// final purge relying only on accumulated whitelist
let cleaned = dropcss({
html: '',
css,
shouldDrop: sel => !whitelist.has(sel),
});
console.log(cleaned.css);
特殊/ 逃脱顺序
DropCSS很愚蠢,会像大众一样,对不寻常的选手窒息 尾风 CSS 框架:
class 属性可以看起来像:
<div class="px-6 pt-6 overflow-y-auto text-base lg:text-sm lg:py-12 lg:pl-6 lg:pr-8 sticky?lg:h-(screen-16)"></div>
<div class="px-2 -mx-2 py-1 transition-fast relative block hover:translate-r-2px hover:text-gray-900 text-gray-600 font-medium"></div>
...CSS是这样的:
.sticky\?lg\:h-\(screen-16\){...}
.lg\:text-sm{...}
.lg\:focus\:text-green-700:focus{...}
哎哟.
解决方案是将 HTML 和 CSS 中的已逃出字符临时替换为一些匹配的独有字符串 /[\w-]/。这使得 DroppCSS 的标注器能够将类名视为一个相接的东西。 经过处理后,我们只需扭转操作.
// remap
let css2 = css
.replace(/\\\:/gm, '__0')
.replace(/\\\//gm, '__1')
.replace(/\\\?/gm, '__2')
.replace(/\\\(/gm, '__3')
.replace(/\\\)/gm, '__4');
let html2 = html.replace(/class=["'][^"']*["']/gm, m =>
m
.replace(/\:/gm, '__0')
.replace(/\//gm, '__1')
.replace(/\?/gm, '__2')
.replace(/\(/gm, '__3')
.replace(/\)/gm, '__4')
);
let res = dropcss({
css: css2,
html: html2,
});
// undo
res.css = res.css
.replace(/__0/gm, '\\:')
.replace(/__1/gm, '\\/')
.replace(/__2/gm, '\\?')
.replace(/__3/gm, '\\(')
.replace(/__4/gm, '\\)');
这个表演的绕行工作可以让DroppCSS在不出问题的情况下处理 Tailwind \o/,并且容易适应支持其他"感兴趣的"案例. 记住一件事 shouldDrop() 将调用含有临时替换的选取器,而不是原选取器,因此如果 shouldDrop() 用于测试某些白名单.
洞穴
- 未测试或设计用于处理不正确的 HTML 或 CSS
- 在您的 HTML 或 CSS 中过度逃出或保留字符可以打破 DroppCSS 的解析器