aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/Minifier.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/Minifier.zig')
-rw-r--r--src/frontend/Minifier.zig170
1 files changed, 170 insertions, 0 deletions
diff --git a/src/frontend/Minifier.zig b/src/frontend/Minifier.zig
new file mode 100644
index 0000000..541bbe1
--- /dev/null
+++ b/src/frontend/Minifier.zig
@@ -0,0 +1,170 @@
+const std = @import("std");
+
+/// A string contained in quotes will not be changed in any form.
+quotes: []const u8,
+
+/// This character is used to escape in strings.
+escape: ?u8,
+
+/// A delimiter does not need any padding around it.
+delimiters: []const u8,
+
+/// Kinds of comments, will be removed in the output.
+comments: []const struct { []const u8, []const u8 },
+
+pub const html: @This() = .{
+ .quotes = "\"'",
+ .escape = null,
+ .delimiters = "<>",
+ .comments = &.{.{ "<!--", "-->" }},
+};
+
+pub const js: @This() = .{
+ .quotes = "\"'`",
+ .escape = '\\',
+ .delimiters = "+-*,.:;[](){}=&|^",
+ .comments = &.{ .{ "//", "\n" }, .{ "/*", "*/" } },
+};
+
+pub const css: @This() = .{
+ .quotes = "\"'",
+ .escape = '\\',
+ .delimiters = ":;{}",
+ .comments = &.{.{ "/*", "*/" }},
+};
+
+fn skip(comptime source: *[]const u8, comptime len: usize) void {
+ source.* = (source.*)[len..];
+}
+
+fn push(
+ comptime source: *[]const u8,
+ comptime target: *[]const u8,
+ comptime len: usize,
+) void {
+ target.* = target.* ++ (source.*)[0..len];
+ skip(source, len);
+}
+
+pub fn minify(
+ comptime self: *const @This(),
+ comptime c: []const u8,
+) []const u8 {
+ @setEvalBranchQuota(c.len * 100);
+ var content = std.mem.trimStart(u8, c, &std.ascii.whitespace);
+ var block: []const u8 = "";
+
+ while (content.len > 0) {
+
+ const had_comment: bool = cmt: {
+ inline for (self.comments) |comment| {
+ const start, const end = comment;
+
+ if (std.mem.startsWith(u8, content, start)) {
+ skip(&content, start.len);
+ skip(&content, if (std.mem.indexOf(u8, content, end)) |l| l + end.len else content.len);
+ break :cmt true;
+ }
+ }
+ break :cmt false;
+ };
+
+ if (had_comment) {
+ } else if (std.mem.indexOfScalar(u8, self.quotes, content[0])) |_| {
+ const quote = content[0];
+ push(&content, &block, 1);
+ while (content.len > 0) {
+ if (content[0] == self.escape) {
+ push(&content, &block, 2);
+ } else if (content[0] == quote) {
+ push(&content, &block, 1);
+ break;
+ }
+ push(&content, &block, 1);
+ }
+ } else {
+ push(&content, &block, 1);
+ }
+
+ const was_trimmed = content.len > 0 and std.ascii.isWhitespace(content[0]);
+ content = std.mem.trimStart(u8, content, &std.ascii.whitespace);
+ if (was_trimmed and
+ block.len > 0 and
+ content.len > 0 and
+ std.mem.indexOfScalar(u8, self.delimiters, block[block.len - 1]) == null and
+ std.mem.indexOfScalar(u8, self.delimiters, content[0]) == null)
+ {
+ block = block ++ " ";
+ }
+ }
+
+ return block;
+}
+
+test "html" {
+ const buffer: []const u8 =
+ \\<html>
+ \\ <head>
+ \\ <title>Some Title</title>
+ \\ </head>
+ \\ <body>
+ \\ <z-component type="something here" tag="1 2 3"> 123 </z-component>
+ \\ <!-- some comment here -->
+ \\ Hello World!
+ \\ </body>
+ \\</html>
+ ;
+
+ try std.testing.expectEqualStrings(
+ "<html><head><title>Some Title</title></head><body><z-component type=\"something here\" tag=\"1 2 3\">123</z-component>Hello World!</body></html>",
+ comptime html.minify(buffer),
+ );
+}
+
+test "css" {
+ const buffer: []const u8 =
+ \\body {
+ \\ background: #fff;
+ \\ color: red;
+ \\}
+ \\
+ \\#content:first {
+ \\ user-select: none;
+ \\ /* comment */
+ \\ content: ' ';
+ \\ padding: 5px 5px;
+ \\}
+ ;
+
+ try std.testing.expectEqualStrings(
+ "body{background:#fff;color:red;}#content:first{user-select:none;content:' ';padding:5px 5px;}",
+ comptime css.minify(buffer),
+ );
+}
+
+test "js" {
+ const buffer: []const u8 =
+ \\class extends HTMLElement {
+ \\ // comment here
+ \\ constructor() {
+ \\ super();
+ \\ this.attachShadow({ mode: 'open' });
+ \\ }
+ \\
+ \\ /*
+ \\ * Mulitline
+ \\ */
+ \\ connectedCallback() {
+ \\ const type = this.getAttribute('type');
+ \\ const template = document.getElementById(`--${type}`);
+ \\ const node = document.importNode(template.content, true);
+ \\ this.shadowRoot.appendChild(node);
+ \\ }
+ \\}
+ ;
+
+ try std.testing.expectEqualStrings(
+ "class extends HTMLElement{constructor(){super();this.attachShadow({mode:'open'});}connectedCallback(){const type=this.getAttribute('type');const template=document.getElementById(`--${type}`);const node=document.importNode(template.content,true);this.shadowRoot.appendChild(node);}}",
+ comptime js.minify(buffer),
+ );
+}