I've tried Yaegi to run the Go inside GoSSR's components without rebuilding the application.

GoSSR keeps a page's Go and markup together, with Go rendering the HTML. I like having the data and its uses in one place. But JavaScript frontend frameworks also give me live updates while I'm editing, and I need those here too.

Especially for CSS.

Change the spacing, look at the page, change it again.... That is a lot of the work of building a website. I build these websites for clients. When somebody is waiting for a finished product and I'm still going back and forth over how the page should look, a rebuild after every small change would make the whole thing miserable. Live updates are something GoSSR needs for me to use it day to day.

Why build my own after trying Yaegi? I want to work on the editing loop all the way into the engine. That means deciding when changed code gets prepared, what work survives between one visitor's request and the next, and how a failure leads me back to the expression I was editing. Building a simple website gets boring for me when all I'm doing is joining familiar pieces together, but having something underneath it to get interested in makes that work easier to keep doing. I get absorbed in that problem. I don't like waiting for a rebuild, so I've given myself an interpreter to finish.

Excellent time management.

Prepare the changed Go

The browser can load an updated stylesheet without running any Go through an interpreter. A change to the component's Go is a different job. So is a template edit which changes the Go it generates. Those changes need to reach the running page too.

Production still uses compiled Go. During development, an interpreter lets me run the changing code inside the server without rebuilding the application, and it needs access to the host functions, types and packages that the component uses.

An interpreter can compile source into instructions it can execute. Preparing those instructions is separate from running them with a visitor's data. For the Go part of an edit, the route looks like this:

changed Go or generated template Go
    + affected dependencies
    → parse and type-check
    → compile an executable representation
    → run with request data
    → render the page

Which expression failed? If a template generates Go that fails, I'd like the interpreter to lead me back to what I was editing without making me search through generated code first.

If the code hasn't changed, what would checking it again buy the next request? I'd be rediscovering its syntax and types while waiting to see the page. The prepared instructions can stay until the page's Go or a dependency changes, which means preparing the affected code again before running it.

Reuse sounds very pleasant.

I can keep the run ready, well, the instructions from it. And the locals? They belong to the last visitor's run. The instructions can stay, but each request needs fresh locals in those slots, while globals and state held by native host code have their own lifetimes for me to manage separately.

Go's parser and type checker supply the frontend, so I can concentrate on representing and executing the checked program without writing another parser for Go. Plenty to learn already.

A small page still brings its packages

Go in the component should still be able to call the packages the page needs. A separate expression language would leave me translating the page's logic into that language as well. I'd rather make the existing calls work.

A page can look straightforward and still stop because a dependency uses Go I haven't implemented. To find out how much language support it needs, I have to follow what its packages use, not only the few lines in its script block.

A component depends on interpreted Go packages and native host functions and values
Figure 1 - The Go a page needs. The component calls Go packages, which can also use native host functions and values. Those connections determine what the interpreter has to support.

This is where the work grows. A feature works on its own, then a closure or native function uses it and there's another connection to work out. Then I start following what those functions call. With my own interpreter I can follow that problem into code I can change.

I also have to finish changing it.

There is still a client deadline. Now I have the tool to finish as well as the product. Sometimes the tool ends up half rushed to meet the product's needs. The pressure keeps the work moving, but it can be heavier than it needed to be. The deadline is entirely unmoved by how interesting I find closures.

I want to keep following those language problems, but I also need to go to the page, change its spacing and see it.

This entry came over from blog.haddon.me. I’ve tidied up the grammar and flow, bringing it closer to how I write now. I used AI to help with the grammar, structure and flow. I have dyslexia, and my writing often comes out as a rambling scrawl, so it can be hard to see what I was actually working on at the time. I think the tidied versions are easier to digest and more interesting to read. The artefacts got the same tidy-up while I was at it.