From a7d0a6c409850b3603abb162fa6fd22d2dfd0177 Mon Sep 17 00:00:00 2001 From: Nathan Reiner Date: Wed, 20 May 2026 17:29:28 +0200 Subject: frontend: add comptime bundler --- src/frontend/Component.zig | 30 +++++++++++ src/frontend/Document.zig | 46 +++++++++++++++++ src/frontend/File.zig | 91 +++++++++++++++++++++++++++++++++ src/frontend/Head.zig | 58 +++++++++++++++++++++ src/frontend/root.zig | 2 + src/frontend/web/hello-world/index.css | 7 +++ src/frontend/web/hello-world/index.html | 3 ++ src/frontend/web/hello-world/index.js | 3 ++ src/frontend/web/index.css | 4 ++ src/frontend/web/index.html | 1 + src/frontend/z-component.js | 17 ++++++ 11 files changed, 262 insertions(+) create mode 100644 src/frontend/Component.zig create mode 100644 src/frontend/Document.zig create mode 100644 src/frontend/File.zig create mode 100644 src/frontend/Head.zig create mode 100644 src/frontend/web/hello-world/index.css create mode 100644 src/frontend/web/hello-world/index.html create mode 100644 src/frontend/web/hello-world/index.js create mode 100644 src/frontend/web/index.css create mode 100644 src/frontend/web/index.html create mode 100644 src/frontend/z-component.js diff --git a/src/frontend/Component.zig b/src/frontend/Component.zig new file mode 100644 index 0000000..d90ed3b --- /dev/null +++ b/src/frontend/Component.zig @@ -0,0 +1,30 @@ +const std = @import("std"); + +const File = @import("File.zig"); + +const Self = @This(); + +name: []const u8, +parent_dir: []const u8, + +pub fn path(p: []const u8) Self { + const name = p[if (std.mem.lastIndexOfScalar( + u8, + std.mem.trim(u8, p, "/"), + '/', + )) |index| index + 1 else 0..]; + + return .{ + .name = name, + .parent_dir = p, + }; +} + +pub fn to_html_string(comptime self: Self) []const u8 { + const html: File.Html = .minify(self.parent_dir ++ "/index.html"); + const js: File.Script = .minify(self.parent_dir ++ "/index.js"); + const css: File.Style = .minify(self.parent_dir ++ "/index.css"); + return ""; +} + +test {} diff --git a/src/frontend/Document.zig b/src/frontend/Document.zig new file mode 100644 index 0000000..0ba46b1 --- /dev/null +++ b/src/frontend/Document.zig @@ -0,0 +1,46 @@ +const std = @import("std"); + +const Component = @import("Component.zig"); +const Head = @import("Head.zig"); +const File = @import("File.zig"); + +const Self = @This(); + +components: []const Component, +head: Head, +styles: []const File.Style, +contents: []const File.Html, + +pub const empty: Self = .{ + .head = .empty, + .components = &.{}, + .styles = &.{}, + .contents = &.{}, +}; + +pub fn to_string(comptime self: Self) []const u8 { + comptime var content: []const u8 = ""; + content = content ++ comptime self.head.to_html_string(self.components, self.styles); + content = content ++ ""; + + inline for (self.contents) |c| { + content = content ++ comptime c.to_html_string(); + } + + content = content ++ ""; + + return content; +} + +test { + const document: Self = comptime .{ + .head = .default("test"), + .components = &.{ + .path("./web/hello-world"), + }, + .styles = &.{}, + .contents = &.{.minify("./web/index.html")}, + }; + + std.debug.print("{s}", .{document.to_string()}); +} diff --git a/src/frontend/File.zig b/src/frontend/File.zig new file mode 100644 index 0000000..e7c3b6d --- /dev/null +++ b/src/frontend/File.zig @@ -0,0 +1,91 @@ +const std = @import("std"); + +pub fn File(comptime tag: ?[]const u8, Iterator: type) type { + const opening = if (tag) |t| "<" ++ t ++ ">" else ""; + const closing = if (tag) |t| "" else ""; + + return struct { + file: []const u8, + do_minify: bool, + + pub fn path(comptime p: []const u8) @This() { + return .{ + .file = p, + .do_minify = false, + }; + } + + pub fn minify(comptime p: []const u8) @This() { + return .{ + .file = p, + .do_minify = true, + }; + } + + fn content(comptime self: @This()) []const u8 { + return opening ++ @embedFile(self.file) ++ closing; + } + + fn content_minified(comptime self: @This()) []const u8 { + var minified: []const u8 = ""; + var iterator: Iterator = .init(@embedFile(self.file)); + + while (iterator.next()) |segment| { + minified = minified ++ segment; + } + + return opening ++ minified ++ closing; + } + + pub fn to_html_string(comptime self: @This()) []const u8 { + return if (self.do_minify) self.content_minified() else self.content(); + } + }; +} + +fn StringIterator(comptime quotes: []const u8) type { + return struct { + content: []const u8, + + pub fn init(content: []const u8) @This() { + return .{ .content = content }; + } + + pub fn next(self: *@This()) ?[]const u8 { + @setEvalBranchQuota(self.content.len * 20); + + self.content = std.mem.trimStart(u8, self.content, &std.ascii.whitespace); + + if (self.content.len == 0) return null; + + var index: usize = 0; + + while (self.content.len > index) { + const char = self.content[index]; + + if (std.mem.indexOfScalar(u8, quotes, char) != null) { + index += 1; + while (self.content.len > index) { + if (self.content[index] == char) { + index += 1; + break; + } + index += 1; + } + } else if (std.ascii.isWhitespace(char)) { + break; + } + index += 1; + } + + const block = self.content[0..index]; + self.content = self.content[index..]; + + return block ++ " "; + } + }; +} + +pub const Style = File("style", StringIterator("\"'")); +pub const Script = File("script", StringIterator("\"'`")); +pub const Html = File(null, StringIterator("\"'")); diff --git a/src/frontend/Head.zig b/src/frontend/Head.zig new file mode 100644 index 0000000..0eb090f --- /dev/null +++ b/src/frontend/Head.zig @@ -0,0 +1,58 @@ +const File = @import("File.zig"); +const Component = @import("Component.zig"); + +const z_component_code: File.Script = .minify("z-component.js"); + +pub const Meta = struct { + name: []const u8, + content: []const u8, + + pub fn to_html_string(comptime self: @This()) []const u8 { + return ""; + } +}; + +title: []const u8, +meta: []const Meta, + +pub const empty: @This() = .{ + .title = "", + .meta = &.{}, +}; + +pub fn default(comptime title: []const u8) @This() { + return .{ + .title = title, + .meta = &.{ + .{ + .name = "viewport", + .content = "width=device-width, initial-scale=1.0", + }, + }, + }; +} + +pub fn to_html_string( + comptime self: @This(), + comptime components: []const Component, + comptime styles: []const File.Style, +) []const u8 { + var content: []const u8 = "" ++ z_component_code.to_html_string(); + + content = content ++ "" ++ self.title ++ ""; + + inline for (components) |component| { + content = content ++ comptime component.to_html_string(); + } + + inline for (styles) |style| { + content = content ++ comptime style.to_html_string(); + } + + inline for (self.meta) |meta| { + content = content ++ comptime meta.to_html_string(); + } + + content = content ++ ""; + return content; +} diff --git a/src/frontend/root.zig b/src/frontend/root.zig index f2cce4c..20c725e 100644 --- a/src/frontend/root.zig +++ b/src/frontend/root.zig @@ -2,6 +2,8 @@ //! which will be embedded right into the ELF file to improve deployment. const std = @import("std"); +pub const Component = @import("Component.zig"); +pub const Document = @import("Document.zig"); test { _ = std.testing.refAllDecls(@This()); diff --git a/src/frontend/web/hello-world/index.css b/src/frontend/web/hello-world/index.css new file mode 100644 index 0000000..41b0b29 --- /dev/null +++ b/src/frontend/web/hello-world/index.css @@ -0,0 +1,7 @@ +#container { + background: #efefef; + padding: 10px; + border-radius: 4px; + user-select: none; + cursor: pointer; +} diff --git a/src/frontend/web/hello-world/index.html b/src/frontend/web/hello-world/index.html new file mode 100644 index 0000000..a9b0be7 --- /dev/null +++ b/src/frontend/web/hello-world/index.html @@ -0,0 +1,3 @@ +
+ Click Me! +
diff --git a/src/frontend/web/hello-world/index.js b/src/frontend/web/hello-world/index.js new file mode 100644 index 0000000..8e55c52 --- /dev/null +++ b/src/frontend/web/hello-world/index.js @@ -0,0 +1,3 @@ +function clicked() { + console.log('clicked'); +} diff --git a/src/frontend/web/index.css b/src/frontend/web/index.css new file mode 100644 index 0000000..a93ea47 --- /dev/null +++ b/src/frontend/web/index.css @@ -0,0 +1,4 @@ +body { + padding: 0; + margin: 0; +} diff --git a/src/frontend/web/index.html b/src/frontend/web/index.html new file mode 100644 index 0000000..885f2c5 --- /dev/null +++ b/src/frontend/web/index.html @@ -0,0 +1 @@ + diff --git a/src/frontend/z-component.js b/src/frontend/z-component.js new file mode 100644 index 0000000..e9b63ff --- /dev/null +++ b/src/frontend/z-component.js @@ -0,0 +1,17 @@ +customElements.define( + "z-component", + 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.id = 'root'; + this.shadowRoot.appendChild(node); + } + } +); -- cgit v1.2.3-70-g09d2