├── .gitignore
├── README.md
├── index.html
├── zigdom.js
├── zigdom.wasm
└── zigdom.zig
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 | zig-cache/zigdom.h
3 | zigdom.o
4 | zigdom.h
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # zig-wasm-dom
2 |
3 | An example demonstrating Zig interacting with the DOM via JS.
4 |
5 | Compile with `zig build-lib zigdom.zig -target wasm32-freestanding -dynamic -OReleaseSmall`
6 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Zig DOM
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/zigdom.js:
--------------------------------------------------------------------------------
1 | const getString = function(ptr, len) {
2 | const slice = zigdom.exports.memory.buffer.slice(ptr, ptr + len);
3 | const textDecoder = new TextDecoder();
4 | return textDecoder.decode(slice);
5 | };
6 |
7 | const pushObject = function(object) {
8 | return zigdom.objects.push(object);
9 | };
10 |
11 | const getObject = function(objId) {
12 | return zigdom.objects[objId - 1];
13 | };
14 |
15 | const dispatch = function(eventId) {
16 | return function() {
17 | zigdom.exports.dispatchEvent(eventId);
18 | };
19 | };
20 |
21 | const elementSetAttribute = function(
22 | node_id,
23 | name_ptr,
24 | name_len,
25 | value_ptr,
26 | value_len
27 | ) {
28 | const node = getObject(node_id);
29 | const attribute_name = getString(name_ptr, name_len);
30 | const value = getString(value_ptr, value_len);
31 | node[attribute_name] = value;
32 | };
33 |
34 | const elementGetAttribute = function(
35 | node_id,
36 | name_ptr,
37 | name_len,
38 | result_address_ptr,
39 | result_address_len_ptr
40 | ) {
41 | const node = getObject(node_id);
42 | const attribute_name = getString(name_ptr, name_len);
43 | const result = node[attribute_name];
44 | // convert result into Uint8Array
45 | const textEncoder = new TextEncoder();
46 | const resultArray = textEncoder.encode(result);
47 | var len = resultArray.length;
48 |
49 | if (len === 0) {
50 | return false;
51 | }
52 |
53 | // allocate required number of bytes
54 | const ptr = zigdom.exports._wasm_alloc(len);
55 | if (ptr === 0) {
56 | throw "Cannot allocate memory";
57 | }
58 |
59 | // write the array to the memory
60 | const mem_result = new DataView(zigdom.exports.memory.buffer, ptr, len);
61 | for (let i = 0; i < len; ++i) {
62 | mem_result.setUint8(i, resultArray[i], true);
63 | }
64 |
65 | // write the address of the result array to result_address_ptr
66 | const mem_result_address = new DataView(
67 | zigdom.exports.memory.buffer,
68 | result_address_ptr,
69 | 32 / 8
70 | );
71 | mem_result_address.setUint32(0, ptr, true);
72 |
73 | //write the size of the result array to result_address_ptr_len_ptr
74 | const mem_result_address_len = new DataView(
75 | zigdom.exports.memory.buffer,
76 | result_address_len_ptr,
77 | 32 / 8
78 | );
79 | mem_result_address_len.setUint32(0, len, true);
80 |
81 | // return if success? (optional)
82 | return true;
83 | };
84 | const eventTargetAddEventListener = function(
85 | objId,
86 | event_ptr,
87 | event_len,
88 | eventId
89 | ) {
90 | const node = getObject(objId);
91 | const ev = getString(event_ptr, event_len);
92 | node.addEventListener(ev, dispatch(eventId));
93 | };
94 |
95 | const documentQuerySelector = function(selector_ptr, selector_len) {
96 | const selector = getString(selector_ptr, selector_len);
97 | return pushObject(document.querySelector(selector));
98 | };
99 |
100 | const documentCreateElement = function(tag_name_ptr, tag_name_len) {
101 | const tag_name = getString(tag_name_ptr, tag_name_len);
102 | return pushObject(document.createElement(tag_name));
103 | };
104 |
105 | const documentCreateTextNode = function(data_ptr, data_len) {
106 | data = getString(data_ptr, data_len);
107 | return pushObject(document.createTextNode(data));
108 | };
109 |
110 | const nodeAppendChild = function(node_id, child_id) {
111 | const node = getObject(node_id);
112 | const child = getObject(child_id);
113 |
114 | if (node === undefined || child === undefined) {
115 | return 0;
116 | }
117 |
118 | return pushObject(node.appendChild(child));
119 | };
120 |
121 | const windowAlert = function(msg_ptr, msg_len) {
122 | const msg = getString(msg_ptr, msg_len);
123 | alert(msg);
124 | };
125 |
126 | const zigReleaseObject = function(object_id) {
127 | zigdom.objects[object_id - 1] = undefined;
128 | };
129 |
130 | const launch = function(result) {
131 | zigdom.exports = result.instance.exports;
132 | if (!zigdom.exports.launch_export()) {
133 | throw "Launch Error";
134 | }
135 | };
136 |
137 | var zigdom = {
138 | objects: [],
139 | imports: {
140 | document: {
141 | query_selector: documentQuerySelector,
142 | create_element: documentCreateElement,
143 | create_text_node: documentCreateTextNode
144 | },
145 | element: {
146 | set_attribute: elementSetAttribute,
147 | get_attribute: elementGetAttribute
148 | },
149 | event_target: {
150 | add_event_listener: eventTargetAddEventListener
151 | },
152 | node: {
153 | append_child: nodeAppendChild
154 | },
155 | window: {
156 | alert: windowAlert
157 | },
158 | zig: {
159 | release_object: zigReleaseObject
160 | }
161 | },
162 | launch: launch,
163 | exports: undefined
164 | };
165 |
--------------------------------------------------------------------------------
/zigdom.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shritesh/zig-wasm-dom/7f5654b058407df44dbce0264ff51e990c84a2fd/zigdom.wasm
--------------------------------------------------------------------------------
/zigdom.zig:
--------------------------------------------------------------------------------
1 | // https://github.com/shritesh/zig-wasm-dom
2 | extern "document" fn query_selector(selector_ptr: [*]const u8, selector_len: usize) usize;
3 | extern "document" fn create_element(tag_name_ptr: [*]const u8, tag_name_len: usize) usize;
4 | extern "document" fn create_text_node(data_ptr: [*]const u8, data_len: usize) usize;
5 | extern "element" fn set_attribute(element_id: usize, name_ptr: [*]const u8, name_len: usize, value_ptr: [*]const u8, value_len: usize) void;
6 | extern "element" fn get_attribute(element_id: usize, name_ptr: [*]const u8, name_len: usize, value_ptr: *[*]u8, value_len: *usize) bool;
7 | extern "event_target" fn add_event_listener(event_target_id: usize, event_ptr: [*]const u8, event_len: usize, event_id: usize) void;
8 | extern "window" fn alert(msg_ptr: [*]const u8, msg_len: usize) void;
9 | extern "node" fn append_child(node_id: usize, child_id: usize) usize;
10 | extern "zig" fn release_object(object_id: usize) void;
11 |
12 | const std = @import("std");
13 |
14 | const eventId = enum(usize) {
15 | Submit,
16 | Clear,
17 | };
18 |
19 | var input_tag_node: u32 = undefined;
20 |
21 | fn launch() !void {
22 | const body_selector = "body";
23 | const body_node = query_selector(body_selector, body_selector.len);
24 | defer release_object(body_node);
25 |
26 | if (body_node == 0) {
27 | return error.QuerySelectorError;
28 | }
29 | const input_tag_name = "input";
30 | input_tag_node = create_element(input_tag_name, input_tag_name.len);
31 | // We don't release as we'll be referencing it later
32 |
33 | if (input_tag_node == 0) {
34 | return error.CreateElementError;
35 | }
36 |
37 | const input_tag_attribute_name = "value";
38 | const input_tag_attribute_value = "Hello from Zig!";
39 | set_attribute(input_tag_node, input_tag_attribute_name, input_tag_attribute_name.len, input_tag_attribute_value, input_tag_attribute_value.len);
40 |
41 | const button_tag_name = "button";
42 |
43 | const submit_button_node = create_element(button_tag_name, button_tag_name.len);
44 | defer release_object(submit_button_node);
45 |
46 | if (submit_button_node == 0) {
47 | return error.CreateElementError;
48 | }
49 |
50 | const event_name = "click";
51 | attach_listener(submit_button_node, event_name, eventId.Submit);
52 |
53 | const submit_text_msg = "submit";
54 | const submit_text_node = create_text_node(submit_text_msg, submit_text_msg.len);
55 | defer release_object(submit_text_node);
56 |
57 | if (submit_text_node == 0) {
58 | return error.CreateTextNodeError;
59 | }
60 |
61 | const attached_submit_text_node = append_child(submit_button_node, submit_text_node);
62 | defer release_object(attached_submit_text_node);
63 |
64 | if (attached_submit_text_node == 0) {
65 | return error.AppendChildError;
66 | }
67 |
68 | const clear_button_node = create_element(button_tag_name, button_tag_name.len);
69 | defer release_object(clear_button_node);
70 |
71 | if (clear_button_node == 0) {
72 | return error.CreateElementError;
73 | }
74 |
75 | attach_listener(clear_button_node, event_name, eventId.Clear);
76 |
77 | const clear_text_msg = "clear";
78 | const clear_text_node = create_text_node(clear_text_msg, clear_text_msg.len);
79 | defer release_object(clear_text_node);
80 |
81 | if (clear_text_node == 0) {
82 | return error.CreateTextNodeError;
83 | }
84 |
85 | const attached_clear_text_node = append_child(clear_button_node, clear_text_node);
86 | defer release_object(attached_clear_text_node);
87 |
88 | if (attached_clear_text_node == 0) {
89 | return error.AppendChildError;
90 | }
91 |
92 | const attached_input_node = append_child(body_node, input_tag_node);
93 | defer release_object(attached_input_node);
94 |
95 | if (attached_input_node == 0) {
96 | return error.AppendChildError;
97 | }
98 |
99 | const attached_submit_button_node = append_child(body_node, submit_button_node);
100 | defer release_object(attached_submit_button_node);
101 |
102 | if (attached_submit_button_node == 0) {
103 | return error.AppendChildError;
104 | }
105 |
106 | const attached_clear_button_node = append_child(body_node, clear_button_node);
107 | defer release_object(attached_clear_button_node);
108 |
109 | if (attached_clear_button_node == 0) {
110 | return error.AppendChildError;
111 | }
112 | }
113 |
114 | fn attach_listener(node: usize, event_name: []const u8, event_id: eventId) void {
115 | add_event_listener(node, event_name.ptr, event_name.len, @enumToInt(event_id));
116 | }
117 |
118 | export fn dispatchEvent(id: u32) void {
119 | switch (@intToEnum(eventId, id)) {
120 | eventId.Submit => on_submit_event(),
121 | eventId.Clear => on_clear_event(),
122 | }
123 | }
124 |
125 | fn on_clear_event() void {
126 | const input_tag_attribute_name = "value";
127 | const input_tag_attribute_value = "";
128 | set_attribute(input_tag_node, input_tag_attribute_name, input_tag_attribute_name.len, input_tag_attribute_value, input_tag_attribute_value.len);
129 | }
130 |
131 | fn on_submit_event() void {
132 | var attribute_ptr: [*]u8 = undefined;
133 | var attribute_len: usize = undefined;
134 |
135 | const input_tag_attribute_name = "value";
136 | const success = get_attribute(input_tag_node, input_tag_attribute_name, input_tag_attribute_name.len, &attribute_ptr, &attribute_len);
137 |
138 | if (success) {
139 | const result = attribute_ptr[0..attribute_len];
140 | defer std.heap.page_allocator.free(result);
141 |
142 | alert(result.ptr, result.len);
143 | }
144 | }
145 |
146 | export fn launch_export() bool {
147 | launch() catch {
148 | return false;
149 | };
150 | return true;
151 | }
152 |
153 | export fn _wasm_alloc(len: usize) u32 {
154 | var buf = std.heap.page_allocator.alloc(u8, len) catch {
155 | return 0;
156 | };
157 | return @ptrToInt(buf.ptr);
158 | }
159 |
--------------------------------------------------------------------------------