vite-ssr-golang-v8 icon indicating copy to clipboard operation
vite-ssr-golang-v8 copied to clipboard

Server returns html document with [object Promise]

Open Nikjee opened this issue 1 year ago • 2 comments

So i was really curious with this go ssr approach and went to try it but there was several issues.

  1. I had problems running docker container, running on M1 MBP and this dockerfile worked for me
FROM node:18-alpine as frontend

RUN npm install -g [email protected]

WORKDIR /app
COPY client ./client/

WORKDIR /app/client
RUN pnpm install --frozen-lockfile && pnpm build


FROM --platform=linux/amd64 ubuntu:latest as backend

# RUN apt update

ARG GO_VERSION
ENV GO_VERSION=${GO_VERSION}

RUN dpkg --add-architecture amd64
RUN apt-get update && apt-get upgrade
RUN apt-get install -y wget git gcc libc-dev make build-essential g++-x86-64-linux-gnu libc6-dev-amd64-cross gcc-aarch64-linux-gnu binutils:amd64 libc6-amd64-cross libstdc++6-amd64-cross

RUN ln -s /usr/x86_64-linux-gnu/lib64/ /lib64

ENV LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/lib64:/usr/x86_64-linux-gnu/lib"
# RUN apt install gcc libc-dev make build-base

RUN wget -P /tmp "https://dl.google.com/go/go1.22.2.linux-amd64.tar.gz"

RUN tar -C /usr/local -xzf "/tmp/go1.22.2.linux-amd64.tar.gz"
RUN rm "/tmp/go1.22.2.linux-amd64.tar.gz"

ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"

WORKDIR /app

COPY --from=frontend /app/dist ./dist

COPY pkg/ ./pkg/
COPY go.mod go.sum Makefile main.go ./
RUN go mod download

RUN make build


FROM --platform=linux/amd64 ubuntu:latest as final

WORKDIR /app

COPY --from=backend /app/build/ /app/

EXPOSE 8080

CMD ["/app/server"]

And Makefile, not sure if it did anything but it is what it is

build:
	# build the backend
	env GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o $(BUILD_DIR)/$(APP_NAME) main.go

  1. As title says server returns a Promise in a string so for that i had to find a way to wait for it, on v8 repo in issues i found this posted
func resolvePromise(ctx *v8go.Context, val *v8go.Value, err error) (*v8go.Value, error) {
	if err != nil || !val.IsPromise() {
		return val, err
	}
	for {
		switch p, _ := val.AsPromise(); p.State() {
		case v8go.Fulfilled:
			return p.Result(), nil
		case v8go.Rejected:
			return nil, errors.New(p.Result().DetailString())
		case v8go.Pending:
			ctx.PerformMicrotaskCheckpoint() // run VM to make progress on the promise
			// go round the loop again...
		default:
			return nil, fmt.Errorf("illegal v8go.Promise state %d", p) // unreachable
		}
	}
}

And in a nutshell what we need to do is this

func (r *Renderer) Render(urlPath string) (string, error) {
	iso := r.pool.Get()
	defer r.pool.Put(iso)

	ctx := v8go.NewContext(iso.Isolate)
	defer ctx.Close()

	iso.RenderScript.Run(ctx)

	renderCmd := fmt.Sprintf(`ssrRender("%s")`, urlPath)
	val, err := ctx.RunScript(renderCmd, r.ssrScriptName)
	result, _ := resolvePromise(ctx, val, err)

	if err != nil {
		if jsErr, ok := err.(*v8go.JSError); ok {
			err = fmt.Errorf("%v", jsErr.StackTrace)
		}
		return "", nil
	}

	return result.String(), nil
}

Maybe for the future there should be a check if a value of a script is a Promise then we wait for it otherwise just return a string.

I'm not sure if you want to add this edits but for people who will come upon this might help

Nikjee avatar May 03 '24 17:05 Nikjee

Ty for posting this. :)

jaybeecave avatar Sep 29 '24 02:09 jaybeecave

Thanks! I updated packages and added check for promise

revenkroz avatar Sep 29 '24 22:09 revenkroz