Skip to content
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

Vite Dev Server implementation #11

Merged
merged 6 commits into from
Mar 15, 2024
Merged
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: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ make rundevserver
# With file-watching so the server auto-restarts and recompiles on Go or TS changes:
WATCH=1 make rundevserver


# The same as rundevserver, recompiles `wowsimcata` binary and runs it on port 3333. Instead of serving content from the dist folder,
# this command also runs `vite serve` to start the Vite dev server on port 3174 (or similar) and automatically reloads the page on .ts changes in less than a second.
# This allows for more rapid development, with sub second reloads on TS changes. This combines the benefits of `WATCH=1 make rundevserver` and `WATCH=1 make host`
# to create something that allows you to work in any part of the code with ease and speed.
# This might get rolled into `WATCH=1 make rundevserver` at some point.
WATCH=1 make devmode

# This is just the same as rundevserver currently
make devmode

# Creates the 'wowsimcata' binary that can host the UI and run simulations natively (instead of with wasm).
# Builds the UI and the compiles it into the binary so that you can host the sim as a server instead of wasm on the client.
# It does this by first doing make dist/cata and then copying all those files to binary_dist/cata and loading all the files in that directory into its binary on compile.
Expand Down
14 changes: 13 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ $(OUT_DIR)/bundle/.dirstamp: \
ui/core/index.ts \
ui/core/proto/api.ts \
$(OUT_DIR)/net_worker.js \
$(OUT_DIR)/sim_worker.js
$(OUT_DIR)/sim_worker.js \
$(OUT_DIR)/local_worker.js
npx tsc --noEmit
npx vite build
touch $@
Expand All @@ -67,6 +68,9 @@ $(OUT_DIR)/sim_worker.js: ui/worker/sim_worker.js
$(OUT_DIR)/net_worker.js: ui/worker/net_worker.js
cp ui/worker/net_worker.js $(OUT_DIR)

$(OUT_DIR)/local_worker.js: ui/worker/local_worker.js
cp ui/worker/local_worker.js $(OUT_DIR)

ui/core/index.ts: $(TS_CORE_SRC)
find ui/core -name '*.ts' | \
awk -F 'ui/core/' '{ print "import \x22./" $$2 "\x22;" }' | \
Expand Down Expand Up @@ -263,3 +267,11 @@ else
# directory just like github pages.
npx http-server $(OUT_DIR)/..
endif

devmode: air devserver
ifeq ($(WATCH), 1)
npx vite serve &
air -tmp_dir "/tmp" -build.include_ext "go,proto" -build.args_bin "--usefs=true --launch=false --wasm=false" -build.bin "./wowsimcata" -build.cmd "make devserver" -build.exclude_dir "assets,dist,node_modules,ui,tools"
else
./wowsimcata --usefs=true --launch=false --host=":3333"
endif
29 changes: 22 additions & 7 deletions sim/web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,11 @@ func (s *server) handleAsyncAPI(w http.ResponseWriter, r *http.Request) {
func (s *server) setupAsyncServer() {
// All async handlers here will call the addNewSim, generating a new UUID and cached progress state.
for route := range asyncAPIHandlers {
http.HandleFunc(route, func(w http.ResponseWriter, r *http.Request) {
s.handleAsyncAPI(w, r)
})
http.Handle(route, corsMiddleware(http.HandlerFunc(s.handleAsyncAPI)))
}

// asyncProgress will fetch the current progress of a simulation by its UUID.
http.HandleFunc("/asyncProgress", func(w http.ResponseWriter, r *http.Request) {
http.Handle("/asyncProgress", corsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
return
Expand Down Expand Up @@ -261,9 +259,20 @@ func (s *server) setupAsyncServer() {
}
w.Header().Add("Content-Type", "application/x-protobuf")
w.Write(outbytes)
})))
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}

func (s *server) runServer(useFS bool, host string, launchBrowser bool, simName string, wasm bool, inputReader *bufio.Reader) {
s.setupAsyncServer()

Expand All @@ -277,7 +286,7 @@ func (s *server) runServer(useFS bool, host string, launchBrowser bool, simName
}

for route := range handlers {
http.HandleFunc(route, handleAPI)
http.Handle(route, corsMiddleware(http.HandlerFunc(handleAPI)))
}

http.HandleFunc("/version", func(resp http.ResponseWriter, req *http.Request) {
Expand Down Expand Up @@ -394,7 +403,6 @@ func handleAPI(w http.ResponseWriter, r *http.Request) {

body, err := io.ReadAll(r.Body)
if err != nil {

return
}
handler, ok := handlers[endpoint]
Expand All @@ -410,6 +418,13 @@ func handleAPI(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
return
}

if googleProto.Equal(msg, msg.ProtoReflect().New().Interface()) {
log.Printf("Request is empty")
w.WriteHeader(http.StatusBadRequest)
return
}

result := handler.handle(msg)

outbytes, err := googleProto.Marshal(result)
Expand Down
63 changes: 63 additions & 0 deletions ui/worker/local_worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
var workerID = "";

addEventListener('message', async e => {
const msg = e.data.msg;
const id = e.data.id;

if (msg == "setID") {
workerID = id;
postMessage({ msg: "idconfirm" })
return;
}
var url = "http://localhost:3333/" + msg;
let response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-protobuf'
},
body: e.data.inputData
});

var content = await response.arrayBuffer();
var outputData;
if (msg == "raidSimAsync" || msg == "statWeightsAsync" || msg == "bulkSimAsync") {
while (true) {
let progressResponse = await fetch("http://localhost:3333/asyncProgress", {
method: 'POST',
headers: {
'Content-Type': 'application/x-protobuf'
},
body: content,
});

// If no new data available, stop querying.
if (progressResponse.status == 204) {
break
}

outputData = await progressResponse.arrayBuffer();
var uint8View = new Uint8Array(outputData);
postMessage({
msg: msg,
outputData: uint8View,
id: id + "progress",
});
await new Promise(resolve => setTimeout(resolve, 500));
}
} else {
outputData = content;
}

var uint8View = new Uint8Array(outputData);
postMessage({
msg: msg,
outputData: uint8View,
id: id,
});

}, false);

// Let UI know worker is ready.
postMessage({
msg: "ready"
});
78 changes: 77 additions & 1 deletion vite.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,83 @@
import path from "path";
import fs from 'fs';
import glob from "glob";
import path from "path";
import { defineConfig } from 'vite'

function serveExternalAssets() {
return {
name: 'serve-external-assets',
configureServer(server) {
server.middlewares.use((req, res, next) => {
const workerMappings = {
'/cata/sim_worker.js': '/cata/local_worker.js',
'/cata/net_worker.js': '/cata/net_worker.js',
'/cata/lib.wasm': '/cata/lib.wasm',
};

if (Object.keys(workerMappings).includes(req.url)) {
const targetPath = workerMappings[req.url];
const assetsPath = path.resolve(__dirname, './dist/cata');
const requestedPath = path.join(assetsPath, targetPath.replace('/cata/', ''));
serveFile(res, requestedPath);
return;
}

if (req.url.includes('/cata/assets')) {
const assetsPath = path.resolve(__dirname, './assets');
const assetRelativePath = req.url.split('/cata/assets')[1];
const requestedPath = path.join(assetsPath, assetRelativePath);

serveFile(res, requestedPath);
return;
} else {
next();
}
});
},
};
}

function serveFile(res, filePath) {
if (fs.existsSync(filePath)) {
const contentType = determineContentType(filePath);
res.writeHead(200, { 'Content-Type': contentType });
fs.createReadStream(filePath).pipe(res);
} else {
console.log("Not found on filesystem: ", filePath)
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
}

function determineContentType(filePath) {
const extension = path.extname(filePath).toLowerCase();
switch (extension) {
case '.jpg':
case '.jpeg':
return 'image/jpeg';
case '.png':
return 'image/png';
case '.gif':
return 'image/gif';
case '.css':
return 'text/css';
case '.js':
return 'text/javascript';
case '.woff':
case '.woff2':
return 'font/woff2';
case '.json':
return 'application/json';
case '.wasm':
return 'application/wasm'; // Adding MIME type for WebAssembly files
// Add more cases as needed
default:
return 'application/octet-stream';
}
}

export default defineConfig(({ command, mode }) => ({
plugins: [serveExternalAssets()],
base: "/cata/",
root: path.join(__dirname, "ui"),
build: {
Expand All @@ -27,6 +102,7 @@ export default defineConfig(({ command, mode }) => ({
},
server: {
origin: 'http://localhost:3000',
// Adding custom middleware to serve 'dist' directory in development
},
}
}));
Loading