|
| 1 | ++++ |
| 2 | +title = "Supercharging your Go presentation with Slidev" |
| 3 | +date = 2024-03-20 |
| 4 | + |
| 5 | +[taxonomies] |
| 6 | +categories = ["tech"] |
| 7 | +tags = ["slides", "golang", "slidev"] |
| 8 | ++++ |
| 9 | + |
| 10 | +# Supercharging your Go presentation with Slidev |
| 11 | + |
| 12 | +I do technical presentations pretty often, and I always try to find new ways to make them more interactive and engaging. |
| 13 | +There were still some things I didn't try yet, like: |
| 14 | +1. Coding my slides. This has always been appealing, but I never managed to take the time to switch from the usual tools I was |
| 15 | + using (Google Slides mostly). |
| 16 | +2. Adding some interactive code samples. I saw some presentation with this kind of feature, I was always impressed. |
| 17 | + |
| 18 | +I had to do a new presentation about Go in my current company and I decided that this was the opportunity I was |
| 19 | +waiting for to tackle those challenges ! |
| 20 | + |
| 21 | +## Coding my slides |
| 22 | + |
| 23 | +I knew it was possible using tools like reveal.js but when I tried to approach it, I found it too close to frontend code |
| 24 | +for my taste. I am not a frontend developer, CSS is a mystery to me and I don't want to spend time on it. |
| 25 | +But recently, I saw a presentation about [sli.dev](https://sli.dev/) at a tech conference and It clicked ! |
| 26 | +[sli.dev](https://sli.dev/) is a tool to create presentations with Markdown and optionally Vue components. |
| 27 | +From their website: |
| 28 | + |
| 29 | +> Slidev aims to provide flexibility and interactivity for developers to make their presentations even more interesting, |
| 30 | +> expressive, and attractive by using the tools and technologies they are already familiar with. |
| 31 | +
|
| 32 | +Starting a new presentation is pretty straightforward, you just have to run the following command: |
| 33 | + |
| 34 | +```bash |
| 35 | +npx create-slidev |
| 36 | +``` |
| 37 | + |
| 38 | +Then you can start the development server with: |
| 39 | + |
| 40 | +```bash |
| 41 | +npm run dev |
| 42 | +``` |
| 43 | + |
| 44 | +Boom, you have a working presentation with a default theme and some example slides. Edit the markdown and you are good to go ! |
| 45 | +If you want to make things a bit more fancy, you can apply a theme or create your own or you can also add some Vue components to your slides. |
| 46 | + |
| 47 | +I won't go into the details of creating a presentation with Slidev, you can find all the information you need on their website. |
| 48 | + |
| 49 | +## Interactive code samples |
| 50 | + |
| 51 | +While creating the presentation I wanted to add some runnable code to the slides |
| 52 | +as advertised in the [documentation](https://sli.dev/guide/demos.html#runnable-code). |
| 53 | + |
| 54 | +Unfortunately, this only works by default with JavaScript and TypeScript (runnable in the browser). |
| 55 | + |
| 56 | +But fear not ! We can still hook our custom code runner to make it work with Go. |
| 57 | + |
| 58 | +### Serving a sandboxed Go environment |
| 59 | + |
| 60 | +It's not directly possible to run Go code in the browser (at least not without some heavy lifting like WebAssembly). |
| 61 | +But we can still run Go code in a sandboxed environment and expose this through an API that the browser can call. |
| 62 | + |
| 63 | +I found a project called [yaegi](https://github.com/traefik/yaegi) maintained by the Traefik team that allows you to run Go code in a sandboxed environment. |
| 64 | +Creating a simple API that runs Go code is pretty straightforward (if you omit the error handling) : |
| 65 | + |
| 66 | +```go |
| 67 | +package main |
| 68 | + |
| 69 | +import ( |
| 70 | + "bytes" |
| 71 | + "encoding/json" |
| 72 | + "io" |
| 73 | + "log" |
| 74 | + "net/http" |
| 75 | + |
| 76 | + "github.com/traefik/yaegi/interp" |
| 77 | + "github.com/traefik/yaegi/stdlib" |
| 78 | +) |
| 79 | + |
| 80 | +func main() { |
| 81 | + // create a http handler and start the server |
| 82 | + http.HandleFunc("/", handleGoCompileRequest) |
| 83 | + err := http.ListenAndServe(":8080", nil) |
| 84 | + if err != nil { |
| 85 | + log.Panic("couldn't start the server", err) |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +func handleGoCompileRequest(w http.ResponseWriter, r *http.Request) { |
| 90 | + // parse the request |
| 91 | + code, err := io.ReadAll(r.Body) |
| 92 | + if err != nil { |
| 93 | + http.Error(w, "couldn't read the request body", http.StatusBadRequest) |
| 94 | + return |
| 95 | + } |
| 96 | + // compile the go code |
| 97 | + var myStdout, myStderr bytes.Buffer |
| 98 | + i := interp.New(interp.Options{ |
| 99 | + Stdout: &myStdout, |
| 100 | + Stderr: &myStderr, |
| 101 | + }) |
| 102 | + _ = i.Use(stdlib.Symbols) |
| 103 | + _, err = i.Eval(string(code)) |
| 104 | + // send the response |
| 105 | + w.Header().Set("Content-Type", "application/json") |
| 106 | + w.Header().Set("Access-Control-Allow-Origin", "*") |
| 107 | + w.WriteHeader(http.StatusOK) |
| 108 | + responseObject := map[string]string{ |
| 109 | + "stdout": myStdout.String(), |
| 110 | + "stderr": myStderr.String(), |
| 111 | + } |
| 112 | + if err != nil { |
| 113 | + responseObject["compilation_error"] = err.Error() |
| 114 | + } |
| 115 | + err = json.NewEncoder(w).Encode(responseObject) |
| 116 | + if err != nil { |
| 117 | + http.Error(w, err.Error(), http.StatusBadRequest) |
| 118 | + } |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +Pretty simple right ? This code will start a server that listens on port 8080 and compiles Go code sent in the request body. |
| 123 | +It will return the stdout and stderr of the program and an error message if the compilation failed. |
| 124 | + |
| 125 | +Of course there are some security concerns with this approach, **DO NOT PUT THIS IN PRODUCTION**. |
| 126 | +You've been warned, but for a simple presentation it should be good enough. |
| 127 | + |
| 128 | +### Mapping the Go code runner in Slidev |
| 129 | + |
| 130 | +Now that we have our Go code runner, we need to instruct Slidev how to use it. |
| 131 | +This is done by creating a custom code runner in the `setup/code-runner.js` file. |
| 132 | + |
| 133 | +```typescript |
| 134 | +import {defineCodeRunnersSetup} from '@slidev/types' |
| 135 | + |
| 136 | +export default defineCodeRunnersSetup(() => { |
| 137 | + return { |
| 138 | + async go(code, ctx) { |
| 139 | + const result = await executeGoCodeRemotely(code) |
| 140 | + if (result.compilation_error) { |
| 141 | + return { |
| 142 | + error: "Compilation error: " + result.compilation_error |
| 143 | + } |
| 144 | + } |
| 145 | + if (result.stderr) { |
| 146 | + return { |
| 147 | + error: "Stderr: " + result.stderr |
| 148 | + } |
| 149 | + } |
| 150 | + return { |
| 151 | + html: result.stdout.replaceAll("\n", "<br>"), |
| 152 | + } |
| 153 | + } |
| 154 | + } |
| 155 | +}) |
| 156 | + |
| 157 | +interface GoResponse { |
| 158 | + stdout: string |
| 159 | + stderr: string |
| 160 | + compilation_error: string |
| 161 | +} |
| 162 | + |
| 163 | +async function executeGoCodeRemotely(code: string): Promise<GoResponse> { |
| 164 | + let request = new Request("http://localhost:8080/", { |
| 165 | + method: "POST", |
| 166 | + body: code, |
| 167 | + cache: "no-cache", |
| 168 | + }); |
| 169 | + return fetch(request) |
| 170 | + .then(response => { |
| 171 | + return response.json() |
| 172 | + }); |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +This code will send the Go code to the server we created earlier and return the stdout, stderr and compilation error if any. |
| 177 | +It will then display the stdout in the slide. |
| 178 | + |
| 179 | +Now in your Markdown file you can use a code block with the `{monaco-run}` directive. |
| 180 | + |
| 181 | +For instance: |
| 182 | +```go {monaco-run} |
| 183 | +import "fmt" |
| 184 | + |
| 185 | +func add(a int, b int) (int, int, int) { |
| 186 | + return a, b, a + b |
| 187 | +} |
| 188 | + |
| 189 | +func main() { |
| 190 | + _, _, sum := add(1, 2) |
| 191 | + fmt.Println(sum) |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +When starting you presentation you should now see the output of the Go code in the slide. |
| 196 | +And you can even change the code live and see the output change ! |
| 197 | + |
| 198 | +## Wrapping up |
| 199 | + |
| 200 | +I am pretty happy with the result, I managed to code my slides and add some interactive code samples. |
| 201 | +The effect was great, the audience was more engaged and I had a lot of fun creating the presentation. |
| 202 | + |
| 203 | +You can find the code of the `Go 101` presentation, with the code runner and the Go server in my [Github repository](https://github.com/ImFlog/go101). |
| 204 | + |
| 205 | +What I would like to do next would be to be able to run things like shell commands in the slides. |
| 206 | +I think it would be a great way to show some real world examples. |
| 207 | + |
| 208 | +But that's for another time ! |
| 209 | + |
| 210 | +Flo. |
0 commit comments