├── .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 | --------------------------------------------------------------------------------