diff options
Diffstat (limited to 'src/z/Minifier.zig')
| -rw-r--r-- | src/z/Minifier.zig | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/z/Minifier.zig b/src/z/Minifier.zig new file mode 100644 index 0000000..65dd9fb --- /dev/null +++ b/src/z/Minifier.zig @@ -0,0 +1,168 @@ +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), + ); +} |