#minify #nodejs #html #minification

hyperbuild

快速无内存分配的 HTML 压缩器,具有智能空白处理

60 个版本

0.2.4 2020年7月11日
0.2.3 2020年7月10日
0.1.12 2020年7月10日
0.1.3 2020年6月19日
0.0.4 2019年12月30日

#173 in 压缩

Download history 4/week @ 2024-03-10 84/week @ 2024-03-31 1/week @ 2024-04-07

156 个月下载量
用于 pagong

MIT 协议

2MB
47K SLoC

hyperbuild

A fast one-pass in-place HTML minifier written in Rust with context-aware whitespace handling.

Also supports JS minification by plugging into esbuild.

可用形式

  • macOS 和 Linux 的 CLI。
  • Rust 库。
  • Node.js、Python、Java 和 Ruby 的原生库。

特性

  • 一次遍历完成压缩,没有回溯或 DOM/AST 构建。
  • 处理过程中不分配额外的堆内存,从而提高性能。
  • 上下文感知的空白处理允许在保留所需空格的同时实现最大压缩。
  • 通过大型测试套件和广泛的 模糊测试 进行充分测试。

性能

Node.js 版本的速度和有效性与 html-minfierminimize 相比,运行在流行的已压缩网页上。更多详情请参阅 bench 文件夹。

Chart showing speed of HTML minifiers Chart showing effectiveness of HTML minifiers

用法

CLI

预编译的二进制文件适用于 x86-64 macOS 和 Linux。

获取

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 项目添加为依赖项,并使用 pippipenv 进行安装。

使用
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 具有高级上下文感知空格压缩功能,可以进行如下操作:

  • precode 中保持空格不变,这些标签对空格敏感。
  • 在内容标签中剪除和合并空格,因为渲染时空格总是会合并。
  • 在布局标签中移除空格,这允许使用内联布局同时保留格式化代码。

方法

有三种空格压缩方法。当处理文本内容时,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 根据假设的元素使用方式来识别元素。通过这些假设,它可以应用最佳的空格压缩策略。

元素 预期子元素
格式化 astrong 和其他元素 格式化元素,文本。
内容 h1p 和其他元素 格式化元素,文本。
布局 divul 和其他元素 布局元素,内容元素。
内容优先 labelli 和其他元素 类似于内容,但可能只有一个子元素作为布局。
格式化元素

空格被压缩。

格式化元素通常是内联元素,它们围绕内容元素中的某些文本,因此其空格不会被修剪,因为它们可能是内容的一部分。

内容元素

空格被修剪和压缩。

内容元素通常代表连续且完整的单位内容,如段落。因此,空格很重要,但连续的空格序列很可能是由于格式化引起的。

之前
<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>
布局元素

空格被修剪和压缩。整个空格被移除。

这些元素应只包含其他元素,不包含任何文本。这使得可以删除整个空白,这在使用: inline-block时非常有用,因为元素之间的空白(例如缩进)不会改变布局和样式。

之前
<ul>↵
··<li>A</li>↵
··<li>B</li>↵
··<li>C</li></ul>
之后
<ul><li>A</li><li>B</li><li>C</li></ul>
内容优先元素

空格被修剪和压缩。

这些元素通常像内容元素,但偶尔也会用作具有一个子元素的布局元素。整个空白不会删除,因为它可能包含内容,但在用作布局时是OK的,因为只有一个子元素,空白会被修剪。

之前
<li>↵
··<article>↵
····<section></section>↵
····<section></section>↵
··</article></li>
之后
<li><article><section></section><section></section></article></li>

标签

可选的闭合标签被删除。

属性

属性值中的任何实体都会解码,然后计算并使用值的最短表示。

  • 双引号,任何"都会被编码。
  • 单引号,任何'都会被编码。
  • 未引用的,任何"/'的第一个字符(如果适用),>的最后一个字符(如果适用),以及任何空白都会被编码。

classd属性(在解码之后)的空白会被修剪并合并。

布尔属性的值会被删除。如果其值在处理之后为空或默认值,一些其他属性也会被完全删除。

对于值为等于JavaScript MIME类型script标签上的type属性会被删除。

如果属性值在处理之后为空,除了名称之外的所有内容都会被完全删除(即没有=),因为空属性隐式地与具有空字符串值的属性相同。

如果可能,会删除属性之间的空格。

实体

如果有效,会解码实体(见相关的解析部分),并且其解码的UTF-8字符长度较短或相等。

不引用有效Unicode标量值的数字实体会被替换为替换字符

如果解码后意外形成了实体,会编码前导的 ampersand,例如&&#97;&#109;&#112;;变为&ampamp;。这样做是因为&amp等于或短于字符实体中所有其他字符的实体表示([&#a-zA-Z0-9;]),并且没有其他以amp开头的冲突实体名称。

在移除注释后,可能会意外地得到一个实体,例如:&am<!-- -->p

在文本中的任何解码后,左箭头会被编码为&LT,如果可能的话;否则为&LT;

注释

注释已被移除。

忽略

感叹号、处理指令和空元素不会被移除,因为这假定它们的声明有特殊原因。

解析

仅支持UTF-8/ASCII编码的HTML代码。

出于性能和代码复杂性的考虑,hyperbuild不支持语法检查或标准执行。

例如,这意味着自闭合标签、声明多个<body>元素、使用不正确的属性名称和值或编写类似于<br>alert('');</br>的内容都不是错误。

然而,有一些语法要求是为了速度和合理性。

标签

标签名称区分大小写。例如,这意味着P不会被视为内容元素,bR不会被考虑为空标签,而Script的内容不会被解析为JavaScript。

标签不得省略。空标签不得有单独的结束标签,例如:</input>

实体

有效的实体会被解码,包括在属性值中。

它们被解释为表示其解码值的字符。这意味着&#9;被视为空白字符,可以压缩。

格式错误的实体被字面地解释为字符序列。

如果命名实体的引用不符合规范,则被视为格式错误。

不引用有效Unicode Scalar Value的数字字符引用被视为格式错误。

属性

反引号(`)不是有效的引号,也不会被解释为引号。然而,在Internet Explorer中,反引号是有效的属性值引号。

某些属性的特别处理需要区分大小写的名称和值。例如,CLASS不会被视为压缩的属性,而type="Text/JavaScript"<script>上不会被移除。

脚本和样式

scriptstyle 标签必须分别用 </script></style> 关闭(区分大小写)。

hyperbuild 不处理 转义和双重转义的 脚本内容。

问题和贡献

欢迎提出拉取请求和任何贡献!

如果 hyperbuild 做了一些意外的事情,误解了一些语法,或者错误地保留/删除了一些代码,请 提出一个问题,并提供一些相关的代码,这些代码可以用来重现和调查问题。

依赖项

约 175-780KB
约 16K SLoC