TreeShking 是通过静态分析的方式找出源码中不会被使用的代码进行删除,达到减小编译打包产物的代码体积的目的。
JS 我们会用 Webpack、Terser 进行 Tree Shking,而 CSS 会用 PurgeCss。
PurgeCss 会分析 html 或其他代码中 css 选择器的使用情况,进而删除没有被使用的 css。
是否对 PurgeCss 怎么找到无用的 css 的原理比较好奇呢?今天我们就来手写个简易版 PurgeCss 来探究下吧。
PurgeCss 要指定 css 应用到哪些 html,它会分析 html 中的 css 选择器,根据分析结果来删除没有用到的 css:
const { PurgeCSS } = require('purgecss')
const purgeCSSResult = await new PurgeCSS().purge({
content: ['**/*.html'],
css: ['**/*.css']
})
我们要做的事情就可以分为两部分:
从 html 中提取信息的部分,叫做 html 提取器(extractor)。
我们可以基于 posthtml 来实现 html 的提取器,它可以做 html 的 parse、分析、转换等,api 和 postcss 类似。
css 的部分使用 postcss,通过 ast 可以分析出每一条 rule。
遍历 css 的 rule,对每个 rule 的选择器都判断下是否在从 html 中提取到选择器中,如果没有,就代表没有被使用,就删掉该选择器。
如果一个 rule 的所有的选择器都删掉了,那么就把这个 rule 删掉。
这就是 purgecss 的实现思路。我们来写下代码。
我们来写一个 postcss 插件来做这件事情,postcss 插件就是基于 AST 做 css 的分析和转换的。
const purgePlugin = (options) => {
return {
postcssPlugin: 'postcss-purge',
Rule (rule) {}
}
}
module.exports = purgePlugin;
postcss 插件的形式是一个函数,接收插件的配置参数,返回一个对象。对象里声明 Rule、AtRule、Decl 等的 listener,也就是对不同 AST 的处理函数。
这个 postcss 插件的名字叫做 purge,可以被这样调用:
const postcss = require('postcss');
const purge = require('./src/index');
const fs = require('fs');
const path = require('path');
const css = fs.readFileSync('./example/index.css');
postcss([purge({
html: path.resolve('./example/index.html'),
})]).process(css).then(result => {
console.log(result.css);
});
通过参数传入 html 的路径,插件里可以通过 option.html 拿到。
接下来我们来实现下这个插件。
前面分析过,实现过程整体分为两步:
我们封装一个 htmlExtractor 来做提取的事情:
const purgePlugin = (options) => {
const extractInfo = {
id: [],
class: [],
tag: []
};
htmlExtractor(options && options.html, extractInfo);
return {
postcssPlugin: 'postcss-purge',
Rule (rule) {}
}
}
module.exports = purgePlugin;
htmlExtractor 的具体实现就是读取 html 的内容,对 html 做 parse 生成 AST,遍历 AST,记录 id、class、tag:
function htmlExtractor(html, extractInfo) {
const content = fs.readFileSync(html, 'utf-8');
const extractPlugin = options => tree => {
return tree.walk(node => {
extractInfo.tag.push(node.tag);
if (node.attrs) {
extractInfo.id.push(node.attrs.id)
extractInfo.class.push(node.attrs.class)
}
return node
});
}
posthtml([extractPlugin()]).process(content);
// 过滤掉空值
extractInfo.id = extractInfo.id.filter(Boolean);
extractInfo.class = extractInfo.class.filter(Boolean);
extractInfo.tag = extractInfo.tag.filter(Boolean);
}
posthtml 的插件形式和 postcss 类似,我们在 posthtml 插件里遍历 AST 并记录了一些信息。
最后,过滤掉 id、class、tag 中的空值,就完成了提取。
我们先不着急做下一步,先来测试下现在的功能。
我们准备这样一个 html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="aaa"></div>
<div id="ccc"></div>
<span></span>
</body>
</html>
测试下提取的信息:
可以看到,id、class、tag 都正确的从 html 中提取了出来。
接下来,我们继续做下一步:从 css 的 AST 中删掉没被使用的部分。
我们声明了 Rule 的 listener,可以拿到 rule 的 AST。要分析的是 selector 部分,需要先根据 “,” 做拆分,然后对每一个选择器做处理。
Rule (rule) {
const newSelector = rule.selector.split(',').map(item => {
// 对每个选择器做转换
}).filter(Boolean).join(',');
if(newSelector === '') {
rule.remove();
} else {
rule.selector = newSelector;
}
}
选择器可以用 postcss-selector-parser 来做 parse、分析和转换。
处理以后的选择器如果都被删掉了,就说明这个 rule 的样式就没用了,就删掉这个 rule。否则可能只是删掉了部分选择器,该样式还会被用到。
const newSelector = rule.selector.split(',').map(item => {
const transformed = selectorParser(transformSelector).processSync(item);
return transformed !== item ? '' : item;
}).filter(Boolean).join(',');
if(newSelector === '') {
rule.remove();
} else {
rule.selector = newSelector;
}
接下来实现对选择器的分析和转换,也就是 transformSelector 函数。
这部分的逻辑就是对每个选择器判断下是否在从 html 提取到的选择器中,如果不在,就删掉。
const transformSelector = selectors => {
selectors.walk(selector => {
selector.nodes && selector.nodes.forEach(selectorNode => {
let shouldRemove = false;
switch(selectorNode.type) {
case 'tag':
if (extractInfo.tag.indexOf(selectorNode.value) == -1) {
shouldRemove = true;
}
break;
case 'class':
if (extractInfo.class.indexOf(selectorNode.value) == -1) {
shouldRemove = true;
}
break;
case 'id':
if (extractInfo.id.indexOf(selectorNode.value) == -1) {
shouldRemove = true;
}
break;
}
if(shouldRemove) {
selectorNode.remove();
}
});
});
};
我们完成了 html 中选择器信息的提取,和 css 根据 html 提取的信息做无用 rule 的删除,插件的功能就已经完成了。
我们来测试下效果:
css:
.aaa, ee , ff{
color: red;
font-size: 12px;
}
.bbb {
color: red;
font-size: 12px;
}
#ccc {
color: red;
font-size: 12px;
}
#ddd {
color: red;
font-size: 12px;
}
p {
color: red;
font-size: 12px;
}
span {
color: red;
font-size: 12px;
}
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="aaa"></div>
<div id="ccc"></div>
<span></span>
</body>
</html>
按理说, p、#ddd、.bbb 的选择器和样式,ee、ff 的选择器都会被删除。
我们使用下该插件:
const postcss = require('postcss');
const purge = require('./src/index');
const fs = require('fs');
const path = require('path');
const css = fs.readFileSync('./example/index.css');
postcss([purge({
html: path.resolve('./example/index.html'),
})]).process(css).then(result => {
console.log(result.css);
});
经测试,功能是对的:
这就是 PurgeCss 的实现原理。我们完成了 css 的 three shaking!
代码上传到了 github:https://github.com/QuarkGluonPlasma/postcss-plugin-exercize
当然,我们只是简易版实现,有的地方做的不完善:
虽然没有做到很完善,但是 PurgeCss 的实现思路已经通了,不是么~
JS 的 TreeShking 使用 Webpack、Terser,而 CSS 的 TreeShking 使用 PurgeCss。
我们实现了一个简易版的 PurgeCss 来理清了它的实现原理:
通过 html 提取器提取 html 中的选择器信息,然后对 CSS 的 AST 做过滤,根据 Rule 的 selector 是否被使用到来删掉没用到的 rule,达到 TreeShking 的目的。
实现这个工具的过程中,我们学习了 postcss 和 posthtml 插件的写法,这两者形式上很类似,只不过一个针对 css 做分析和转换,一个针对 html。
Postcss 可以分析和转换 CSS,比如这里的删除无用 css 就是一个很好的应用。你还见过别的 postcss 的很棒的应用场景么,不妨一起来讨论下吧~
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/3-nxykPmocpL57kNy33BHg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。