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 = \\ \\ \\ Some Title \\ \\ \\ 123 \\ \\ Hello World! \\ \\ ; try std.testing.expectEqualStrings( "Some Title123Hello World!", 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), ); }