-
-
Notifications
You must be signed in to change notification settings - Fork 295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhancements needed for using templ inside a Wasm environment #1001
Comments
Thanks for compiling your research into such a well written report. I'll take a read through, but want to give it the proper thinking time rather than rushing it, so won't be in the next couple of days. |
Is the intent here related to building a Templ Playground Editor with Monaco and LSP off the files on the server .? That can be done by passing the files up from the server and then back down to the server , with the templ generator in watch mode . The LSP can be sent over SSS , like Datastar . So can the file tree and even file change diffs . Datastar is kind of perfect for this . this is really not doing any compilation in the browser but just allowing a collaborative editor , and a decent playground . —- Also , the Monaco Editor is designed for the LSP to be pushed via Websockets , which is not so aligned with Datastar . So a little server side converter is the best way perhaps . Not sure as I have not delved into the templ LSP enough . am happy to dig into it, but need some clarification of the above things |
I'm struggling to understand this, probably because I've never tried to run Go in Web Assembly. I think that some examples would be useful! FWIW, there's a WASM based templ playground at https://play.templ.guide/ You can see the code at https://github.com/templ-go/playground Not sure if that helps... |
I've also stumbled upon this, i'll make another example: templ MyExample(user string, show bool) {
<script>
function writeUserName(el,name,show){
if (!show){
return;
}
el.innerHTML=name
}
</script>
<div
onClick="writeUserName(this,'test',true)"
// I'd like to inject the values directly
// onClick="writeUserName(this,{user},{show})"
style="width: 200px; height: 200px; background-color: #BEBEBE; color: black;"
>
placeholder
</div>
} A workaround could be: templ MyExample(user string, show bool) {
<script>
function proxy(el) {
const user = el.getAttribute('arg-user');
const show = el.getAttribute('arg-show') !== null;
writeUserName(el, user, show);
}
function writeUserName(el, name, show) {
if (!show) {
return;
}
el.innerHTML = name;
}
</script>
<div
onClick="proxy(this)"
arg-user={ user }
arg-show?={ show }
style="
width: 200px;
height: 200px;
background-color: #bebebe;
color: black;
"
>
placeholder
</div>
} To me this is not very intuitive and pretty distant from the general approach: "open a pair of curly brackets and dump some text into some html" Anyways, thanks for your work and please correct me if i'm wrong 😃 |
Thanks for the input I will try out the suggestions. |
Hey @shaban I just want to frame this issue, because the context was not explained. I think some of the confusion is related to the context. You want to run templ on your laptop ( or ci ) so that you can import the output of templ into a golang package that is then compiled ( again on your laptop ) to WASM and then run that WASM in a web browser ? To jump ahead , assuming this context is correct, it seems that it would require that the templ generator is told that it’s compiling for web WASM, and constructs the js sys calls correctly. https://github.com/inkeliz/go_inkwasm Generates these js sys calls is such a way as to give a performance increase. It’s relevant to this use case , not just for performance , but also because a generator of the js sys calls. Thus it can be used as a reference for how templ could generate these js sys calls. looking forward to see where this goes . I think optimising the developer experience for using templ is a web WASM environment is a worthy use case these days. I my self use a lot of golang inside Service Workers and Web workers , and the easier it can be to allow templ to be used in these environments the better. To do this a proper test harness would be needed because there are varying ways a web WASM can be setup. there are other runtime contexts , like Cloudflare workers too in which templ can be used . So the way the inputs into the developers templ code is constructed varies , and this the generator would vary. See: https://github.com/syumai/workers/tree/main/_examples/hello where the querystring is the context of the inputs that would be sent into temp. This all begs the question of , should templ generate this shim code to gather the inputs into the templ code . I think it’s worth exploring and reflecting on it. My own WASM code when compiled with tinygo is half the size ( in general) then golang compiled WASM, and is often about 1 mb for small projects . It’s usable for some developers projects imho. You can also break the project down into a WASM per web page ( which I do actually , and what is done for golang that will run in a browser or cloudflare workers ) and the service worker shim can async load them when needed. Once loaded he next call is super quick of course . I mention all this to help layout the wider context for how this can work in reality with decent performance for the users. |
I also want to draw your attention to this article. https://blog.boot.dev/golang/running-go-in-the-browser-wasm-web-workers/ It shows how to load the WASM into browser based web workers so that your web WASM app is multi threaded . Any attempt to use templ should probably take this course. It’s breaking the web WASM into smaller chunks and so lowers the initial load times for users . It’s also speeding up the apps user experience by multi threading the app. It’s very similar to the approach used with Cloudflare workers. I use this approach for golang WASM on the Server, Cloudflare workers and the Browser, so that I have reasonable load times . The runtime context, impacts how you approach the compile time , in that each has a different shim of how you pass inputs into the golang code later. So, it again begs the question of if a generator is needed that runs before templ, so that the shim can be constructed for the context ? It’s what I do for most golang that will be run in different WASM runtime contexts. The code generated by this shim is then used to pass the variables into templ. here is a golang generator that does exactly that , that is designed for go templates , that does web workers and share web workers : https://github.com/magodo/go-wasmww/tree/main What I like about it is that it’s using stdio as the “ transport “, which makes it highly flexible , in that the actual worker code can be used in the browser, or on a server or Cloudflare workers, etc. it’s essentially using a conduit that’s isomorphic / agnostic . I write all my golang that will be used in any WASM context to use stdio. It also makes it very easy to write tests and run the code outside a WASM context . I generate a golang based cli, that can call the WASM just for this . |
Hello, a while back I promised to check back with findings on trying out templ inside a Wasm environment. So here are my findings:
Preface
Use Cases
Most of us coming from Go have the notion of Go as a backend language but when using Wasm things are totally different.
We now have a backend and platform agnostic frontend language and as such we can in theory use Go as a type safe compiled language in all browser environments with a few considerations. So the most viable use cases are as a web frontend for a fully fledged computer application, since in a localhost environment bundle sizes of 10MB+ do not matter. Also if your Go Wasm Application brings something powerful to the table you might get away with a load screen of a few seconds.
Examples
So I mentioned Wasm being agnostic of the backend means that you can use it inside a QT web view, Wails, possibly even Tauri or Electron if you wanted since with the net/http package and encoding/json you can communicate with all backends amicably. This opens up a wide array of possibilities, since the only real integration with the backend that your Wasm frontend needs is the same definition of the data model.
Workflow
While experimenting I used wails as a backend but found that while developing your frontend this is not the best approach since you don't want the extra overhead on the turnaround times that wails introduces, but simply using vite or any other capable development server is preferable, since the only thing we need is something that will give us access to our assets and data.
Problems
Event Handling
In wasm you basically have two choices for event handling:
templ
Assumptions
in templ it is assumed that the html is created by the server which means that all the scripts that we create and special constructs like the script directive in templ templates are correctly evaluated and executed.
Reality
Component wide the html gets delivered in a string builder and then set to the components html element with a snippet like this:
This way the containing script will not be evaluated which renders most JS examples in templ.guide useless.
So the only way to make it work is by applying the script separately like so:
Issue
I have not found a way to construct a javascript call dynamically like
The quotation marks keep the variable from being evaluated and single quotes don't work either. I could be wrong but I don't see any way how to dynamically create parameters to give context to an event besides looking for custom attributes in the event target element.
If that is by design and intended forgive my ignorance but since JS conversions are costly I thought a string based approach would be much more practical especially when you consider that the receiver might be a fantastic javascript library that does not know anything about your custom data attributes.
Build Tags
To be able to use syscall/js package one has to specify build tags like so.
//go:build js && wasm
In a wasm environment it is totally acceptable and even a crucial feature to use other go files in the same package space as the generated template files to host utility functions and generally not pollute the template with lengthy code.
But this is only possible when they both share the same build tags also imports won't work if they don't share compatible build tags.
So when generating the go file build tags must either must be respected or if that is a problem at least there should be a command line flag like
that prepends the build tag separated with a newline from the "Do not edit" comment.
tldr;
Go, traditionally a backend language, takes on a new role in the WASM environment. It becomes a platform-agnostic frontend language capable of powering rich web applications. This shift opens possibilities for using Go in various contexts, including:
Development Workflow
While frameworks like Wails offer integrated development, a simpler setup with tools like
vite
can be more efficient for frontend development. This approach focuses on rapid iteration and asset management.Dynamic JavaScript Calls
Constructing dynamic JavaScript calls with
templ
is problematic due to quotation mark escaping issues. This limits the ability to pass context to event handlers, potentially hindering integration with JavaScript libraries.Finding a way to manage javascript in a centralized way would be beneficial for everyone using templ since that would solve innerHTML issues and improve JS Code organization. I will not propose a solution since this is out of scope of this writeup.
Build Tags
Using the
syscall/js
package requires specific build tags://go:build js && wasm
This poses challenges when:
Proposed Solution
Introduce a command-line flag to templ for specifying build tags in generated files:
This would prepend the specified build tag to the generated Go files, ensuring compatibility and code organization.
Conclusion
Addressing these issues will enable templ's usability in WASM environments, allow developers to leverage its strengths for building complex and interactive applications, with the added benefit of not having to deal with javascript focused build steps (node modules, dependency issues)
P.S. Apologies you were right about the watch flag causing the problems in my last issue :)
The text was updated successfully, but these errors were encountered: