快速无分配HTML压缩器,具有智能空白处理
项目描述
hyperbuild
一个快速的、单次遍历原地HTML压缩器,用Rust编写,具有上下文感知的空白处理。
还支持通过esbuild插件进行JS压缩。
提供以下形式
- 适用于macOS和Linux的命令行界面。
- Rust库。
- 适用于Node.js、Python、Java和Ruby的本地库。
功能
- 压缩在单次遍历中完成,没有回溯或DOM/AST构建。
- 处理过程中不分配额外的堆内存,从而提高了性能。
- 上下文感知的空白处理允许最大程度的压缩,同时保留所需的空间。
- 经过大型测试套件和广泛的模糊测试,测试良好。
性能
与html-minfier和minimize相比,Node.js版本的运行速度和有效性,在流行的已压缩网页上运行。有关更多详细信息,请参阅bench文件夹。
用法
命令行界面
提供适用于x86-64 macOS和Linux的预编译二进制文件。
获取
使用
使用--help
参数获取更多详细信息。
hyperbuild --src /path/to/src.html --out /path/to/output.min.html
API
Rust
获取
[dependencies]
hyperbuild = { version = "0.2.4", features = ["js-esbuild"] }
使用js-esbuild
功能构建需要安装Go编译器,以构建JS压缩器。
如果未启用js-esbuild
功能,则cfg.minify_js
将没有效果。
使用
use hyperbuild::{Cfg, FriendlyError, hyperbuild, hyperbuild_copy, hyperbuild_friendly_error, hyperbuild_truncate};
fn main() {
let mut code = b"<p> Hello, world! </p>".to_vec();
let cfg = &Cfg {
minify_js: false,
};
// Minifies a slice in-place and returns the new minified length,
// but leaves any original code after the minified code intact.
match hyperbuild(&mut code, cfg) {
Ok(minified_len) => {}
Err((error_type, error_position)) => {}
};
// Creates a vector copy containing only minified code
// instead of minifying in-place.
match hyperbuild_copy(&code, cfg) {
Ok(minified) => {}
Err((error_type, error_position)) => {}
};
// Minifies a vector in-place, and then truncates the
// vector to the new minified length.
match hyperbuild_truncate(&mut code, cfg) {
Ok(()) => {}
Err((error_type, error_position)) => {}
};
// Identical to `hyperbuild` except with FriendlyError instead.
// `code_context` is a string of a visual representation of the source,
// with line numbers and position markers to aid in debugging syntax.
match hyperbuild_friendly_error(&mut code, cfg) {
Ok(minified_len) => {}
Err(FriendlyError { position, message, code_context }) => {
eprintln!("Failed at character {}:", position);
eprintln!("{}", message);
eprintln!("{}", code_context);
}
};
}
Node.js
hyperbuild 在 npm 上 可用,作为一个 Node.js 原生模块,并支持 Node.js 8 及以上版本。
获取
使用 npm
npm i hyperbuild
使用 Yarn
yarn add hyperbuild
使用
const hyperbuild = require("hyperbuild");
const cfg = { minifyJs: false };
const minified = hyperbuild.minify("<p> Hello, world! </p>", cfg);
// Alternatively, minify in place to avoid copying.
const source = Buffer.from("<p> Hello, world! </p>", cfg);
hyperbuild.minifyInPlace(source);
hyperbuild 也适用于 TypeScript
import * as hyperbuild from "hyperbuild";
import * as fs from "fs";
const cfg = { minifyJs: false };
const minified = hyperbuild.minify("<p> Hello, world! </p>", cfg);
hyperbuild.minifyInPlace(fs.readFileSync("source.html"), cfg);
Java
hyperbuild 通过 JNI 提供,并支持 Java 7 及以上版本。
获取
作为 Maven 依赖添加
<dependency>
<groupId>in.wilsonl.hyperbuild</groupId>
<artifactId>hyperbuild</artifactId>
<version>0.2.4</version>
</dependency>
使用
import in.wilsonl.hyperbuild.Hyperbuild;
Hyperbuild.Configuration cfg = new Hyperbuild.Configuration.Builder()
.setMinifyJs(false)
.build();
try {
String minified = Hyperbuild.minify("<p> Hello, world! </p>", cfg);
} catch (Hyperbuild.SyntaxException e) {
System.err.println(e.getMessage());
}
// Alternatively, minify in place:
assert source instanceof ByteBuffer && source.isDirect();
Hyperbuild.minifyInPlace(source, cfg);
Python
hyperbuild 在 PyPI 上可用,作为一个 原生模块,并支持 CPython (默认 Python 解释器) 3.5 及以上版本。
获取
将 PyPI 项目作为依赖项添加,并使用 pip
或 pipenv
进行安装。
使用
import hyperbuild
try:
minified = hyperbuild.minify("<p> Hello, world! </p>", minify_js=False)
except SyntaxError as e:
print(e)
Ruby
hyperbuild 已发布于 RubyGems,作为一个适用于 macOS 和 Linux 的 原生模块,并支持 Ruby 2.5 及以上版本。
获取
将库作为依赖项添加到 Gemfile
或 *.gemspec
。
使用
require 'hyperbuild'
print Hyperbuild.minify("<p> Hello, world! </p>", { :minify_js => false })
压缩
空白字符
hyperbuild 具有高级的上下文感知空白字符压缩功能,例如:
- 在
pre
和code
中保留空白字符,它们对空白字符敏感。 - 在内容标签中裁剪和合并空白字符,因为渲染时空白字符已经被合并。
- 在布局标签中移除空白字符,这允许使用内联布局同时保持格式化代码。
方法
有三种空白字符压缩方法。在处理文本内容时,hyperbuild 根据包含元素选择使用哪种方法。
合并空白字符
适用对象:除 空白字符敏感 元素之外的所有元素。
将文本节点中的空白字符序列减少到单个空格 (U+0020)。
之前 | 之后 |
---|---|
<p>↵
··The·quick·brown·fox↵
··jumps·over·the·lazy↵
··dog.↵
</p>
|
<p>·The·quick·brown·fox·jumps·over·the·lazy·dog.·</p>
|
移除整个空白字符
移除位于标签之间且只包含空白字符的任何文本节点。
之前 | 之后 |
---|---|
<ul>↵
··<li>A</li>↵
··<li>B</li>↵
··<li>C</li>↵
</ul>
|
<ul>↵
··<li>A</li><li>B</li><li>C</li>↵
</ul>
|
裁剪空白字符
移除任何标签的前导/尾随空白字符。
之前 | 之后 |
---|---|
<p>↵
··Hey,·I·<em>just</em>·found↵
··out·about·this·<strong>cool</strong>·website!↵
··<sup>[1]</sup>↵
</p>
|
<p>Hey,·I·<em>just</em>·found↵
··out·about·this·<strong>cool</strong>·website!↵
··<sup>[1]</sup></p>
|
元素类型
hyperbuild 根据假设的元素使用方式来识别元素。通过这些假设,它可以应用最佳空白字符压缩策略。
分组 | 元素 | 期望子元素 |
---|---|---|
格式化 | a 、strong 和其他元素 |
格式化元素,文本。 |
内容 | h1 、p 和其他元素 |
格式化元素,文本。 |
布局 | div 、ul 和其他元素 |
布局元素,内容元素。 |
内容优先 | label 、li 和其他元素 |
类似于内容,但可能只有一个子元素时可以作为布局。 |
格式化元素
空白字符被合并。
格式化元素通常是内联元素,它们围绕内容元素中的某些文本进行包装,因此其空白字符不会被裁剪,因为它们可能是内容的一部分。
内容元素
空白字符被裁剪和合并。
内容元素通常代表一个连续的完整内容单元,如段落。因此,空白字符很重要,但它们的序列很可能是由于格式化导致的。
之前
<p>↵
··Hey,·I·<em>just</em>·found↵
··out·about·this·<strong>cool</strong>·website!↵
··<sup>[1]</sup>↵
</p>
之后
<p>Hey,·I·<em>just</em>·found·out·about·this·<strong>cool</strong>·website!·<sup>[1]</sup></p>
布局元素
空白字符被裁剪和合并。整个空白字符被移除。
这些元素应只包含其他元素,不包含任何文本。这使得可以移除整个空白字符,这在使用 display: inline-block
时非常有用,这样元素之间的空白字符(例如缩进)就不会改变布局和样式。
之前
<ul>↵
··<li>A</li>↵
··<li>B</li>↵
··<li>C</li>↵
</ul>
之后
<ul><li>A</li><li>B</li><li>C</li></ul>
内容优先元素
空白字符被裁剪和合并。
这些元素通常像内容元素一样,但偶尔也会用作具有一个子元素的布局元素。整个空白不会删除,因为它可能包含内容,但作为布局使用是可以的,因为只有一个子元素,空白会被修剪。
之前
<li>↵
··<article>↵
····<section></section>↵
····<section></section>↵
··</article>↵
</li>
之后
<li><article><section></section><section></section></article></li>
标签
可选的闭合标签会被删除。
属性
属性值中的任何实体都会解码,然后计算并使用值的简短表示。
- 双引号,其中的任何
"
都会被编码。 - 单引号,其中的任何
'
都会被编码。 - 未加引号,其中的
"
/'
的第一个字符(如果适用),>
的最后一个字符(如果适用),以及任何空白都会被编码。
class
和 d
属性在其解码后的空白会被修剪并合并。
布尔属性值会被删除。如果其值在处理后的结果为空或默认值,则一些其他属性会被完全删除。
script
标签上的 type
属性,其值等于一个 JavaScript MIME 类型,会被删除。
如果属性值在处理后的结果为空,则除了名称之外的所有内容都会被完全删除(即没有 =
),因为空属性隐式地与具有空字符串值的属性相同。
如果可能,会在属性之间删除空白。
实体
如果有效,实体会被解码(参见相关解析部分),并且其解码字符作为 UTF-8 的长度更短或相等。
不引用有效 Unicode 标量值 的数字实体会被替换为 替换字符。
如果解码后无意中形成了一个实体,则编码前导的 ampersand,例如 &amp;
变为 &amp;
。这是因为它等于或短于所有其他字符的实体表示形式([&#a-zA-Z0-9;]
),并且没有其他以 amp
开头的冲突实体名称。
在删除注释后可能会得到一个无意中的实体,例如 &am<!-- -->p
。
在解码后的文本中的左尖括号(<
)会被编码为 <
,如果可能的话,否则为 <
。
注释
注释会被删除。
忽略
感叹号、处理指令和空元素不会被删除,因为假设它们的声明有特殊原因。
解析
仅支持 UTF-8/ASCII 编码的 HTML 代码。
由于性能和代码复杂性的原因,hyperbuild 不进行语法检查或标准强制。
例如,这意味着自闭合标签、声明多个 <body>
元素、使用错误的属性名称和值或写入 <br>alert('');</br>
这样的内容都不是错误。
然而,有一些语法要求是为了速度和合理性。
标签
标签名是区分大小写的。例如,这意味着 P
不会被视为内容元素,bR
不会被视为空标签,并且 Script
的内容不会被解析为 JavaScript。
标签不得被省略。空标签不得有单独的闭合标签,例如 </input>
。
实体
格式良好的实体会被解码,包括在属性值中。
它们会被解释为代表其解码值的字符。这意味着 	
被视为空白字符,可以被最小化。
格式不正确的实体会被字面地解释为一组字符。
如果命名实体按照规范是一个无效引用,则被视为格式不正确。
不引用有效Unicode标量值的数字字符引用被认为是格式错误的。
属性
反引号(`
)不是有效的引号,也不会被当作引号处理。然而,在Internet Explorer中,反引号是有效的属性值引号。
某些属性的特别处理需要区分大小写的名称和值。例如,CLASS
不会被识别为压缩的属性,且在<script>
上的type="Text/JavaScript"
也不会被删除。
脚本和样式
script
和style
标签必须分别以</script>
和</style>
关闭(区分大小写)。
hyperbuild不处理转义和双重转义的脚本内容。
问题和贡献
欢迎拉取请求和任何贡献!
如果hyperbuild做了预期之外的事情,误解了某些语法,或者错误地保留了/删除了某些代码,请在这里提出问题,并附上一些相关的代码,以便重现和调查问题。
项目详情
下载文件
下载您平台上的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。