Skip to content

Stevenmasley/wasm #71

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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
GO_SRC_FILES := $(shell find . $(FIND_EXCLUSIONS) -type f -name '*.go' -not -name '*_test.go')

.PHONY: gen
gen:
@echo "Generating code..."
go generate ./...

.PHONY: clean-testdata
clean-testdata:
git clean -xfd testdata
git clean -xfd testdata

.PHONY: build-wasm
build-wasm: site/public/build/preview.wasm
mkdir -p ./site/public/build/

site/public/build/preview.wasm: $(GO_SRC_FILES)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a trailing newline to appease github (and me) pls

GOOS=js GOARCH=wasm go build -o site/public/build/preview.wasm ./cmd/wasm
87 changes: 87 additions & 0 deletions cmd/wasm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//go:build js && wasm

package main

import (
"context"
"encoding/json"
"fmt"
"io/fs"
"path/filepath"
"syscall/js"

"github.com/spf13/afero"

"github.com/coder/preview"
"github.com/coder/preview/types"
)

func main() {
// Create a channel to keep the Go program alive
done := make(chan struct{}, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
done := make(chan struct{}, 0)
done := make(chan struct{})

it's fine for unbuffered to be implied imo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey this is a draft for a reason!

To be 100% transparent, this was almost all written by cursor lol


// Expose the Go function `fibonacciSum` to JavaScript
js.Global().Set("go_preview", js.FuncOf(tfpreview))
js.Global()

// Block the program from exiting
<-done
}

func tfpreview(this js.Value, p []js.Value) any {
tf, err := fileTreeFS(p[0])
if err != nil {
return err
}

output, diags := preview.Preview(context.Background(), preview.Input{
PlanJSONPath: "",
PlanJSON: nil,
ParameterValues: nil,
Owner: types.WorkspaceOwner{},
}, tf)

data, _ := json.Marshal(map[string]any{
"output": output,
"diags": diags,
})
return js.ValueOf(string(data))
}

func fileTreeFS(value js.Value) (fs.FS, error) {
data := js.Global().Get("JSON").Call("stringify", value).String()
var filetree map[string]any
if err := json.Unmarshal([]byte(data), &filetree); err != nil {
return nil, err
}

mem := afero.NewMemMapFs()
loadTree(mem, filetree)

return afero.NewIOFS(mem), nil
}

func loadTree(mem afero.Fs, fileTree map[string]any, path ...string) {
dir := filepath.Join(path...)
err := mem.MkdirAll(dir, 0755)
if err != nil {
fmt.Printf("error creating directory %q: %v\n", dir, err)
}
for k, v := range fileTree {
switch vv := v.(type) {
case string:
fn := filepath.Join(dir, k)
f, err := mem.Create(fn)
if err != nil {
fmt.Printf("error creating file %q: %v\n", fn, err)
continue
}
_, _ = f.WriteString(vv)
f.Close()
case map[string]any:
loadTree(mem, vv, append(path, k)...)
default:
fmt.Printf("unknown type %T for %q\n", v, k)
}
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/hashicorp/terraform-exec v0.22.0
github.com/hashicorp/terraform-json v0.24.0
github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/spf13/afero v1.12.0
github.com/stretchr/testify v1.10.0
github.com/zclconf/go-cty v1.16.2
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
3 changes: 3 additions & 0 deletions site/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# wasm
public/build/preview.wasm
Comment on lines +26 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# wasm
public/build/preview.wasm
# build artifacts
public/build/

2 changes: 1 addition & 1 deletion site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<link href="/src/index.css" rel="stylesheet">
<title>Conditional Parameters</title>
</head>
<body>
<body>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👻

<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
1 change: 1 addition & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"react-dom": "^19.0.0",
"react-hook-form": "^7.54.2",
"react-json-view": "^1.21.3",
"react-router-dom": "^7.5.0",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7"
Expand Down
57 changes: 57 additions & 0 deletions site/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file added site/public/build/placeholder
Empty file.
10 changes: 9 additions & 1 deletion site/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import './App.css'
import { DemoPage } from './DemoPage'
import { Live } from './components/Live'
import { ThemeProvider } from "./components/theme-provider"
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { PreviewProvider } from './contexts/PreviewContext/PreviewContext'

function App() {
return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<DemoPage />
<BrowserRouter>
<Routes>
<Route path="/" element={<DemoPage />} />
<Route path="/live" element={<PreviewProvider><Live /> </PreviewProvider>} />
</Routes>
</BrowserRouter>
</ThemeProvider>
);
}
Expand Down
30 changes: 30 additions & 0 deletions site/src/components/Live.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { usePreview } from '../contexts/PreviewContext/PreviewContext';
import { useState } from 'react';

export function Live() {
const { isWasmLoaded, preview } = usePreview();
const [previewResult, setPreviewResult] = useState<string | null>(null);

const handlePreviewClick = async () => {
try {
const result = await preview();
setPreviewResult(result);
} catch (error) {
console.error('Error calling preview:', error);
}
};

return (
<div>
{isWasmLoaded && <p>Wasm Loaded</p>}
{!isWasmLoaded && <p>Wasm not Loaded</p>}

<button onClick={handlePreviewClick}>Run Preview</button>
{previewResult !== null && (
<div>
<p>Preview Result: {previewResult}</p>
</div>
)}
</div>
);
}
58 changes: 58 additions & 0 deletions site/src/contexts/PreviewContext/PreviewContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createContext, FC, PropsWithChildren, useContext, useEffect, useState } from "react";
import "./wasm_exec.js";

export interface PreviewContextValue {
isWasmLoaded: boolean;
preview?: GoPreviewDef;
}

export const PreviewContext = createContext<PreviewContextValue | undefined>(
undefined,
);

export const PreviewProvider: FC<PropsWithChildren> = ({ children }) => {
const [isWasmLoaded, setIsWasmLoaded] = useState(false);

// useEffect hook to load WebAssembly when the component mounts
useEffect(() => {
// Function to asynchronously load WebAssembly
async function loadWasm(): Promise<void> {
// Create a new Go object
const goWasm = new window.Go();
const result = await WebAssembly.instantiateStreaming(
// Fetch and instantiate the main.wasm file
fetch('build/preview.wasm'),
// Provide the import object to Go for communication with JavaScript
goWasm.importObject
);
// Run the Go program with the WebAssembly instance
goWasm.run(result.instance);
setIsWasmLoaded(true);
}

loadWasm();
}, []);



return (
<PreviewContext.Provider
value={{
isWasmLoaded,
preview: window.go_preview,
}}
>
{children}
</PreviewContext.Provider>
);
}

export const usePreview = (): PreviewContextValue => {
const context = useContext(PreviewContext);

if (!context) {
throw new Error("usePreview should be used inside of <PreviewProvider />");
}

return context;
};
Loading
Loading