aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig44
-rw-r--r--flake.nix1
-rw-r--r--src/frontend/Component.zig30
-rw-r--r--src/frontend/Document.zig46
-rw-r--r--src/frontend/File.zig41
-rw-r--r--src/frontend/Head.zig58
-rw-r--r--src/frontend/root.zig13
-rw-r--r--src/frontend/web/hello-world/index.css7
-rw-r--r--src/frontend/web/hello-world/index.html6
-rw-r--r--src/frontend/web/hello-world/index.js3
-rw-r--r--src/frontend/web/index.html3
-rw-r--r--src/frontend/z-component.js16
-rw-r--r--src/html/Element.zig104
-rw-r--r--src/html/root.zig7
-rw-r--r--src/http/Server.zig40
-rw-r--r--src/main.zig25
-rw-r--r--src/web/badge/index.css5
-rw-r--r--src/web/badge/index.html3
-rw-r--r--src/web/badge/index.js1
-rw-r--r--src/web/badge/root.zig8
-rw-r--r--src/web/index.css (renamed from src/frontend/web/index.css)0
-rw-r--r--src/web/index.html3
-rw-r--r--src/web/index.js0
-rw-r--r--src/z/Component.zig18
-rw-r--r--src/z/File.zig18
-rw-r--r--src/z/Minifier.zig (renamed from src/frontend/Minifier.zig)0
-rw-r--r--src/z/root.zig52
-rw-r--r--src/z/z.js78
28 files changed, 392 insertions, 238 deletions
diff --git a/build.zig b/build.zig
index db5a525..989f6c4 100644
--- a/build.zig
+++ b/build.zig
@@ -22,11 +22,17 @@ pub fn build(b: *std.Build) void {
.target = target,
});
- const frontend_mod = b.addModule("frontend", .{
- .root_source_file = b.path("src/frontend/root.zig"),
+ const html_mod = b.addModule("html", .{
+ .root_source_file = b.path("src/html/root.zig"),
.target = target,
});
+ const z_mod = b.addModule("z", .{
+ .root_source_file = b.path("src/z/root.zig"),
+ .target = target,
+ });
+ z_mod.addImport("html", html_mod);
+
const http_mod = b.addModule("http", .{
.root_source_file = b.path("src/http/root.zig"),
.target = target,
@@ -43,7 +49,8 @@ pub fn build(b: *std.Build) void {
.imports = &.{
.{ .name = "db", .module = db_mod },
.{ .name = "api", .module = api_mod },
- .{ .name = "frontend", .module = frontend_mod },
+ .{ .name = "z", .module = z_mod },
+ .{ .name = "html", .module = html_mod },
.{ .name = "http", .module = http_mod },
}
}),
@@ -78,12 +85,19 @@ pub fn build(b: *std.Build) void {
api_test_step.dependOn(&run_api_tests.step);
- const frontend_tests = b.addTest(.{
- .root_module = frontend_mod,
+ const z_tests = b.addTest(.{
+ .root_module = z_mod,
+ });
+ const run_z_tests = b.addRunArtifact(z_tests);
+ const z_test_step = b.step("test-z", "Run z tests");
+ z_test_step.dependOn(&run_z_tests.step);
+
+ const html_tests = b.addTest(.{
+ .root_module = html_mod,
});
- const run_frontend_tests = b.addRunArtifact(frontend_tests);
- const frontend_test_step = b.step("test-frontend", "Run frontend tests");
- frontend_test_step.dependOn(&run_frontend_tests.step);
+ const run_html_tests = b.addRunArtifact(html_tests);
+ const html_test_step = b.step("test-html", "Run html tests");
+ html_test_step.dependOn(&run_html_tests.step);
const http_tests = b.addTest(.{
@@ -103,7 +117,8 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run all tests");
test_step.dependOn(&run_db_tests.step);
test_step.dependOn(&run_api_tests.step);
- test_step.dependOn(&run_frontend_tests.step);
+ test_step.dependOn(&run_z_tests.step);
+ test_step.dependOn(&run_html_tests.step);
test_step.dependOn(&run_http_tests.step);
test_step.dependOn(&run_exe_tests.step);
@@ -130,4 +145,15 @@ pub fn build(b: *std.Build) void {
.source_dir = exe.getEmittedDocs(),
});
docs_step.dependOn(&docs_install.step);
+
+
+ const hot_cmd = b.addSystemCommand(&.{
+ "sh",
+ "-c",
+ "clear; echo 'building in hot mode'; find src -type f | entr -r zig build run",
+ });
+
+ const hot_step = b.step("hot", "Run server in hot reload mode");
+ hot_step.dependOn(&hot_cmd.step);
+ run_cmd.step.dependOn(b.getInstallStep());
}
diff --git a/flake.nix b/flake.nix
index a461455..92ff2d9 100644
--- a/flake.nix
+++ b/flake.nix
@@ -29,6 +29,7 @@
packages = [
pkgs.zig
pkgs.sqlite
+ pkgs.entr
];
};
};
diff --git a/src/frontend/Component.zig b/src/frontend/Component.zig
deleted file mode 100644
index d90ed3b..0000000
--- a/src/frontend/Component.zig
+++ /dev/null
@@ -1,30 +0,0 @@
-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 "<template id=\"--" ++ self.name ++ "\">" ++ css.to_html_string() ++ html.to_html_string() ++ js.to_html_string() ++ "</template>";
-}
-
-test {}
diff --git a/src/frontend/Document.zig b/src/frontend/Document.zig
deleted file mode 100644
index 0ba46b1..0000000
--- a/src/frontend/Document.zig
+++ /dev/null
@@ -1,46 +0,0 @@
-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 = "<!DOCTYPE html><html>";
- content = content ++ comptime self.head.to_html_string(self.components, self.styles);
- content = content ++ "<body>";
-
- inline for (self.contents) |c| {
- content = content ++ comptime c.to_html_string();
- }
-
- content = content ++ "</body></html>";
-
- 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
deleted file mode 100644
index fac6a03..0000000
--- a/src/frontend/File.zig
+++ /dev/null
@@ -1,41 +0,0 @@
-const Minifier = @import("Minifier.zig");
-
-pub fn File(comptime tag: ?[]const u8, comptime minifier: Minifier) type {
- const opening = if (tag) |t| "<" ++ t ++ ">" else "";
- const closing = if (tag) |t| "</" ++ 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,
- };
- }
-
- pub fn to_html_string(comptime self: @This()) []const u8 {
- return opening ++ (if (self.do_minify)
- minifier.minify(@embedFile(self.file))
- else
- @embedFile(self.file)) ++ closing;
- }
- };
-}
-
-/// When using regex in Javascript the spaces
-/// might get trimmed since /re/ is a awful syntax
-/// I don't what to add a rule for it currently,
-/// Since a single `/` is a operator but if there are two on one line
-/// it is a regex.
-pub const Script = File("script", .js);
-pub const Style = File("style", .css);
-pub const Html = File(null, .html);
diff --git a/src/frontend/Head.zig b/src/frontend/Head.zig
deleted file mode 100644
index 0eb090f..0000000
--- a/src/frontend/Head.zig
+++ /dev/null
@@ -1,58 +0,0 @@
-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 "<meta name=\"" ++ self.name ++ "\" content=\"" ++ self.content ++ "\">";
- }
-};
-
-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 = "<head>" ++ z_component_code.to_html_string();
-
- content = content ++ "<title>" ++ self.title ++ "</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 ++ "</head>";
- return content;
-}
diff --git a/src/frontend/root.zig b/src/frontend/root.zig
deleted file mode 100644
index 8ff1055..0000000
--- a/src/frontend/root.zig
+++ /dev/null
@@ -1,13 +0,0 @@
-//! Implementation of the frontend contains mainly javascript files
-//! 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");
-pub const File = @import("File.zig");
-pub const Head = @import("Head.zig");
-pub const Minifier = @import("Minifier.zig");
-
-test {
- _ = std.testing.refAllDecls(@This());
-}
diff --git a/src/frontend/web/hello-world/index.css b/src/frontend/web/hello-world/index.css
deleted file mode 100644
index 41b0b29..0000000
--- a/src/frontend/web/hello-world/index.css
+++ /dev/null
@@ -1,7 +0,0 @@
-#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
deleted file mode 100644
index 047a283..0000000
--- a/src/frontend/web/hello-world/index.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<div id="container" onclick="clicked()">
- Click Me!
- <z-component type="box">
- <span slot="title">Hello World!<span>
- </z-component>
-</div>
diff --git a/src/frontend/web/hello-world/index.js b/src/frontend/web/hello-world/index.js
deleted file mode 100644
index 8e55c52..0000000
--- a/src/frontend/web/hello-world/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-function clicked() {
- console.log('clicked');
-}
diff --git a/src/frontend/web/index.html b/src/frontend/web/index.html
deleted file mode 100644
index 646cdb0..0000000
--- a/src/frontend/web/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<z-component type="hello-world">
- <p slot="ur-mom">lulululu</p>
-</z-component>
diff --git a/src/frontend/z-component.js b/src/frontend/z-component.js
deleted file mode 100644
index 5e430b5..0000000
--- a/src/frontend/z-component.js
+++ /dev/null
@@ -1,16 +0,0 @@
-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.appendChild(node);
- }
- }
-);
diff --git a/src/html/Element.zig b/src/html/Element.zig
new file mode 100644
index 0000000..8ecf31e
--- /dev/null
+++ b/src/html/Element.zig
@@ -0,0 +1,104 @@
+const Self = @This();
+
+pub const Content = union(enum) {
+ elements: []const Self,
+ string: []const u8,
+ void: void,
+
+ pub fn content(string: []const u8) @This() {
+ return .{ .string = string };
+ }
+
+ pub fn children(elements: []const Self) @This() {
+ return .{ .elements = elements };
+ }
+};
+
+pub const Attribute = struct {
+ name: []const u8,
+ value: []const u8,
+};
+
+tag: []const u8,
+attributes: []const Attribute,
+content: Content,
+
+fn attributesFrom(attributes: anytype) []const Attribute {
+ const T = @TypeOf(attributes);
+ const info = @typeInfo(T);
+
+ if (info != .@"struct") {
+ @compileError("struct needed");
+ }
+
+ const fields = info.@"struct".fields;
+
+ var attrs: []const Attribute = &.{};
+
+ for (fields) |field| {
+ const name = field.name;
+ const value = @field(attributes, name);
+
+ const next_attr: []const Attribute = &.{ .{ .name = name, .value = value } };
+ attrs = attrs ++ next_attr;
+ }
+
+ return attrs;
+}
+
+pub fn native(name: @EnumLiteral(), attrs: anytype, content: Content) Self {
+ return .{
+ .tag = @tagName(name),
+ .attributes = attributesFrom(attrs),
+ .content = content,
+ };
+}
+
+pub fn transparent(content: Content) Self {
+ return .{
+ .tag = "",
+ .attributes = &.{},
+ .content = content,
+ };
+}
+
+pub fn toHtml(comptime self: Self) []const u8 {
+ var string: []const u8 = string: {
+ if (self.tag.len == 0) {
+ break :string "";
+ }
+
+ var string: []const u8 = "<" ++ self.tag;
+
+ inline for (self.attributes) |attr| {
+ string = string ++ " " ++ attr.name ++ "='" ++ attr.value ++ "'";
+ }
+
+ string = string ++ ">";
+
+ break :string string;
+ };
+
+
+ switch (self.content) {
+ .void => {},
+ .string => |s| {
+ string = string ++ s ++ "</" ++ self.tag ++ ">";
+ },
+ .elements => |els| {
+ for (els) |el| {
+ string = string ++ el.toHtml();
+ }
+
+ string = string ++ "</" ++ self.tag ++ ">";
+ }
+ }
+
+ string = string ++ if (self.tag.len == 0) "" else "</" ++ self.tag ++ ">";
+
+ return string;
+}
+
+pub fn toDocument(comptime self: Self) []const u8 {
+ return "<!DOCTYPE html>" ++ self.toHtml();
+}
diff --git a/src/html/root.zig b/src/html/root.zig
new file mode 100644
index 0000000..14a9b3c
--- /dev/null
+++ b/src/html/root.zig
@@ -0,0 +1,7 @@
+const std = @import("std");
+
+pub const Element = @import("Element.zig");
+
+test {
+ _ = std.testing.refAllDecls(@This());
+}
diff --git a/src/http/Server.zig b/src/http/Server.zig
index b6e2d90..71037bb 100644
--- a/src/http/Server.zig
+++ b/src/http/Server.zig
@@ -3,18 +3,48 @@ const std = @import("std");
const Self = @This();
server: std.Io.net.Server,
-io: std.Io.Threaded,
+threaded: std.Io.Threaded,
pub fn init(
gpa: std.mem.Allocator,
address: std.Io.net.IpAddress,
) !Self {
- var io: std.Io.Threaded = .init(gpa, .{});
+ var threaded: std.Io.Threaded = .init(gpa, .{});
return .{
- .io = io,
- .server = try address.listen(io.io(), .{ .reuse_address = true }),
+ .threaded = threaded,
+ .server = try address.listen(threaded.io(), .{ .reuse_address = true }),
};
}
-pub fn serve(_: *Self) !void {}
+pub fn serve(self: *Self, content: []const u8) !void {
+ var read_buffer: [1024 * 8]u8 = undefined;
+ var write_buffer: [1024 * 8]u8 = undefined;
+
+ while (true) {
+ const stream = try self.server.accept(self.threaded.io());
+ defer stream.close(self.threaded.io());
+
+ var reader = stream.reader(self.threaded.io(), &read_buffer);
+ var writer = stream.writer(self.threaded.io(), &write_buffer);
+
+ var server: std.http.Server = .init(&reader.interface, &writer.interface);
+ var request = server.receiveHead() catch |e| {
+ std.debug.print("error: {}\n", .{e});
+ continue;
+ };
+
+ std.debug.print("{s} {s}\n", .{ @tagName(request.head.method), request.head.target });
+
+ if (std.mem.eql(u8, request.head.target, "/")) {
+ request.respond(content, .{
+ .status = .ok,
+ .extra_headers = &.{
+ .{ .name = "Content-Type", .value = "text/html" },
+ },
+ }) catch continue;
+ } else {
+ request.respond("Not Found", .{ .status = .not_found }) catch continue;
+ }
+ }
+}
diff --git a/src/main.zig b/src/main.zig
index df0c061..f17214c 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -5,10 +5,33 @@
const std = @import("std");
const http = @import("http");
+const html = @import("html");
+const z = @import("z");
+
+const badge = @import("web/badge/root.zig");
+
+const document: html.Element = .native(.html, .{}, .children(&.{
+ .native(.head, .{}, .children(&.{
+ .native(.title, .{}, .content("Memora")),
+ .native(.meta, .{
+ .name = "viewport",
+ .content = "width=device-width, initial-scale=1.0",
+ }, .void),
+ z.env(&.{
+ badge.component,
+ }),
+ })),
+ .native(.body, .{}, .children(&.{
+ z.html.load(@embedFile("web/index.html")),
+ z.component(.badge, .{}, .children(&.{
+ .native(.span, .{ .slot = "content" }, .content("Hello Z (direct)!")),
+ })),
+ })),
+}));
pub fn main(init: std.process.Init) !void {
var server: http.Server = try .init(init.gpa, try .parseLiteral("127.0.0.1:8080"));
- try server.serve();
+ try server.serve(comptime document.toDocument());
}
test {
diff --git a/src/web/badge/index.css b/src/web/badge/index.css
new file mode 100644
index 0000000..3f237d6
--- /dev/null
+++ b/src/web/badge/index.css
@@ -0,0 +1,5 @@
+#container {
+ background: red;
+ padding: 5px;
+ border-radius: 5px;
+}
diff --git a/src/web/badge/index.html b/src/web/badge/index.html
new file mode 100644
index 0000000..dcb38e1
--- /dev/null
+++ b/src/web/badge/index.html
@@ -0,0 +1,3 @@
+<div id="container">
+ <slot name="content"></slot>
+</div>
diff --git a/src/web/badge/index.js b/src/web/badge/index.js
new file mode 100644
index 0000000..60fbac5
--- /dev/null
+++ b/src/web/badge/index.js
@@ -0,0 +1 @@
+Z.by_id('sdf')
diff --git a/src/web/badge/root.zig b/src/web/badge/root.zig
new file mode 100644
index 0000000..5e8ea25
--- /dev/null
+++ b/src/web/badge/root.zig
@@ -0,0 +1,8 @@
+const z = @import("z");
+
+pub const component: z.Component = .{
+ .name = .badge,
+ .body = @embedFile("index.html"),
+ .style = @embedFile("index.css"),
+ .script = @embedFile("index.js"),
+};
diff --git a/src/frontend/web/index.css b/src/web/index.css
index a93ea47..a93ea47 100644
--- a/src/frontend/web/index.css
+++ b/src/web/index.css
diff --git a/src/web/index.html b/src/web/index.html
new file mode 100644
index 0000000..3bf9953
--- /dev/null
+++ b/src/web/index.html
@@ -0,0 +1,3 @@
+<z-component type="badge">
+ <span slot="content">Hello Z!</span>
+</z-component>
diff --git a/src/web/index.js b/src/web/index.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/web/index.js
diff --git a/src/z/Component.zig b/src/z/Component.zig
new file mode 100644
index 0000000..3211335
--- /dev/null
+++ b/src/z/Component.zig
@@ -0,0 +1,18 @@
+const std = @import("std");
+const html = @import("html");
+const z = @import("root.zig");
+
+name: @EnumLiteral(),
+body: []const u8,
+style: []const u8,
+script: []const u8,
+
+pub fn toElement(comptime self: @This()) html.Element {
+ return .transparent(.children(&.{
+ .native(.template, .{ .id = "@" ++ @tagName(self.name) }, .children(&.{
+ z.style.load(self.style),
+ z.body.load(self.body),
+ })),
+ z.script.load("Zenv.constructors['component__" ++ @tagName(self.name) ++ "'] = (Z) => {" ++ self.script ++ "}"),
+ }));
+}
diff --git a/src/z/File.zig b/src/z/File.zig
new file mode 100644
index 0000000..cd7094f
--- /dev/null
+++ b/src/z/File.zig
@@ -0,0 +1,18 @@
+const Minifier = @import("Minifier.zig");
+const Element = @import("html").Element;
+
+tag: ?@EnumLiteral(),
+minifier: Minifier,
+
+pub fn load(comptime self: @This(), comptime content: []const u8) Element {
+ if (self.tag) |tag| {
+ return .native(
+ tag,
+ .{},
+ .content(comptime self.minifier.minify(content)),
+ );
+ } else {
+ return .transparent(.content(comptime self.minifier.minify(content)));
+ }
+}
+
diff --git a/src/frontend/Minifier.zig b/src/z/Minifier.zig
index 65dd9fb..65dd9fb 100644
--- a/src/frontend/Minifier.zig
+++ b/src/z/Minifier.zig
diff --git a/src/z/root.zig b/src/z/root.zig
new file mode 100644
index 0000000..26bbffc
--- /dev/null
+++ b/src/z/root.zig
@@ -0,0 +1,52 @@
+const std = @import("std");
+
+pub const Minifier = @import("Minifier.zig");
+pub const Component = @import("Component.zig");
+pub const File = @import("File.zig");
+
+const Element = @import("html").Element;
+
+pub const script: File = .{
+ .tag = .script,
+ .minifier = .js,
+};
+pub const style: File = .{
+ .tag = .style,
+ .minifier = .css,
+};
+pub const body: File = .{
+ .tag = .body,
+ .minifier = .html,
+};
+
+pub const html: File = .{
+ .tag = null,
+ .minifier = .html,
+};
+
+pub fn env(comptime components: []const Component) Element {
+ var children: []const Element = &.{script.load(@embedFile("z.js"))};
+
+ inline for (components) |c| {
+ const child: []const Element = &.{c.toElement()};
+ children = children ++ child;
+ }
+
+ return .transparent(.children(children));
+}
+
+pub fn component(kind: @EnumLiteral(), attrs: anytype, content: Element.Content) Element {
+ var element: Element = .native(.@"z-component", attrs, content);
+
+ const type_attr: []const Element.Attribute = &.{.{
+ .name = "type",
+ .value = @tagName(kind),
+ }};
+ element.attributes = type_attr ++ element.attributes;
+
+ return element;
+}
+
+test {
+ _ = std.testing.refAllDecls(@This());
+}
diff --git a/src/z/z.js b/src/z/z.js
new file mode 100644
index 0000000..a5a1b92
--- /dev/null
+++ b/src/z/z.js
@@ -0,0 +1,78 @@
+class ZComponent extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ }
+
+ connectedCallback() {
+ const type = this.getAttribute('type');
+
+ if (type == null) {
+ throw `component without any type given.`;
+ }
+
+ const template = document.getElementById(`@${type}`);
+
+ if (template == null) {
+ throw `component '${type}' does not exist.`;
+ }
+
+ const node = document.importNode(template.content, true);
+ this.shadowRoot.appendChild(node);
+
+ Zenv.constructors[`component__${type}`](new Zenv(this));
+ }
+}
+
+class Zenv {
+ static constructors = {};
+
+ constructor(doc) {
+ this.document = doc;
+ }
+
+ component(type, slots) {
+ const element = document.createElement('z-component');
+ element.setAttribute('type', type);
+
+ for (const name in slots) {
+ const slot = slots[name];
+ slot.setAttribute('slot', name);
+ element.appendChild(slot);
+ }
+
+ return element;
+ }
+
+ populate_attributes(obj, attrs) {
+ for (const attr in attrs) {
+ if (typeof(obj[attr]) == 'object' && typeof(attrs[attr]) == 'object') {
+ populate_attributes(obj[attr], attrs[attr]);
+ } else {
+ obj[attr] = attrs[attr];
+ }
+ }
+ }
+
+ native(name, options) {
+ const element = document.createElement(name);
+ Zenv.populate_attributes(element, options);
+ return element;
+ }
+
+ by_id(id) {
+ return this.document.shadowRoot.getElementById(id);
+ }
+
+ by_selector(query) {
+ return this.document.shadowRoot.querySelector(query);
+ }
+
+ by_selector_all(query) {
+ return this.document.shadowRoot.querySelectorAll(query);
+ }
+}
+
+Z = new Zenv(document.body);
+
+customElements.define("z-component", ZComponent);