Skip to content

Commit

Permalink
Merge pull request zeabur#135 from zeabur/yuanlin/zea-1828-zbpack-imp…
Browse files Browse the repository at this point in the history
…lementation

feat(lib): Serverless output for static websites and Next.js
  • Loading branch information
yuaanlin authored Sep 15, 2023
2 parents 3def036 + 2d0e446 commit 5a88021
Show file tree
Hide file tree
Showing 17 changed files with 517 additions and 180 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/otiai10/copy v1.12.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/moznion/go-optional v0.10.0 h1:YE42pzLDp6vc9zi/2hyaHYJesjahZEgFXEN1u5DMwMA=
github.com/moznion/go-optional v0.10.0/go.mod h1:l3mLmsyp2bWTvWKjEm5MT7lo3g5MRlNIflxFB0XTASA=
github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=
github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
github.com/pan93412/envexpander v1.1.0 h1:cf57P8BllM2CWfHO0eEKkpIoz0e44J94+H2Mh+hcUtw=
github.com/pan93412/envexpander v1.1.0/go.mod h1:sBLOiYNNmCNyx+6z6L3Cegcs1MG0l7KXu8rhe41ZsM0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
Expand Down
51 changes: 3 additions & 48 deletions internal/nodejs/__snapshots__/template_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ RUN yarn install
# Build if we can build it



EXPOSE 8080
CMD yarn start


---

[TestTemplate_NBuildCmd_OutputDir_NSPA - 1]
Expand All @@ -34,21 +32,8 @@ RUN yarn install
# Build if we can build it



FROM nginx:alpine as runtime

COPY --from=build /src/dist /usr/share/nginx/html/static
RUN echo "\
server { \
listen 8080; \
root /usr/share/nginx/html/static; \
absolute_redirect off; \
location / { \
try_files \$uri \$uri.html \$uri/index.html /404.html =404; \
} \
}"> /etc/nginx/conf.d/default.conf
EXPOSE 8080

CMD yarn start

---

Expand All @@ -66,21 +51,8 @@ RUN yarn install
# Build if we can build it



FROM nginx:alpine as runtime

COPY --from=build /src/dist /usr/share/nginx/html/static
RUN echo "\
server { \
listen 8080; \
root /usr/share/nginx/html/static; \
absolute_redirect off; \
location / { \
try_files \$uri /index.html; \
} \
}"> /etc/nginx/conf.d/default.conf
EXPOSE 8080

CMD yarn start

---

Expand All @@ -98,11 +70,9 @@ RUN yarn install
# Build if we can build it
RUN yarn build


EXPOSE 8080
CMD yarn start


---

[TestTemplate_BuildCmd_OutputDir - 1]
Expand All @@ -119,21 +89,8 @@ RUN yarn install
# Build if we can build it
RUN yarn build


FROM nginx:alpine as runtime

COPY --from=build /src/dist /usr/share/nginx/html/static
RUN echo "\
server { \
listen 8080; \
root /usr/share/nginx/html/static; \
absolute_redirect off; \
location / { \
try_files \$uri /index.html; \
} \
}"> /etc/nginx/conf.d/default.conf
EXPOSE 8080

CMD yarn start

---

Expand All @@ -157,9 +114,7 @@ RUN bun install
# Build if we can build it



EXPOSE 8080
CMD bun start main.ts


---
86 changes: 86 additions & 0 deletions internal/nodejs/nextjs/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package nextjs

import (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strings"

cp "github.com/otiai10/copy"
)

// constructNextFunction will construct the first function page, used as symlinks for other function pages
func constructNextFunction(zeaburOutputDir, firstFuncPage string) error {
p := path.Join(zeaburOutputDir, "functions", firstFuncPage+".func")

err := os.MkdirAll(p, 0755)
if err != nil {
return fmt.Errorf("create function dir: %w", err)
}

launcher, err := renderLauncher()
if err != nil {
return fmt.Errorf("render launcher: %w", err)
}

err = os.WriteFile(path.Join(p, "index.js"), []byte(launcher), 0644)
if err != nil {
return fmt.Errorf("write launcher: %w", err)
}

err = cp.Copy(".next", path.Join(p, ".next"))
if err != nil {
return fmt.Errorf("copy .next: %w", err)
}

err = cp.Copy("package.json", path.Join(p, "package.json"))
if err != nil {
return fmt.Errorf("copy package.json: %w", err)
}

outputNodeModulesDir := path.Join(p, "node_modules")
err = os.MkdirAll(outputNodeModulesDir, 0755)
if err != nil {
return fmt.Errorf("create node_modules dir: %w", err)
}

var deps []string
err = filepath.Walk(".next", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".nft.json") {
type nftJSON struct {
Files []string `json:"files"`
}
b, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read nft.json: %w", err)
}
var nft nftJSON
err = json.Unmarshal(b, &nft)
if err != nil {
return fmt.Errorf("unmarshal nft.json: %w", err)
}
for _, file := range nft.Files {
if !strings.Contains(file, "node_modules") {
continue
}
file = file[strings.Index(file, "node_modules"):]
deps = append(deps, file)
}
}
return nil
})
if err != nil {
return fmt.Errorf("walk .next: %w", err)
}

for _, dep := range deps {
err = cp.Copy(dep, path.Join(p, dep))
if err != nil {
return fmt.Errorf("copy dep: %w", err)
}
}

return nil
}
68 changes: 68 additions & 0 deletions internal/nodejs/nextjs/launcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package nextjs

import (
_ "embed"
"encoding/json"
"fmt"
"os"
"strings"
"text/template"
)

//go:embed launcher.js.tmpl
var launcherTemplate string

// getNextConfig read .next/required-server-files.json and return the config string that will be injected into launcher
func getNextConfig() (string, error) {
rsf, err := os.ReadFile(".next/required-server-files.json")
if err != nil {
return "", fmt.Errorf("read required-server-files.json: %w", err)
}

type requiredServerFiles struct {
Config json.RawMessage `json:"config"`
}

var rs requiredServerFiles
err = json.Unmarshal(rsf, &rs)
if err != nil {
return "", fmt.Errorf("unmarshal required-server-files.json: %w", err)
}

var data map[string]interface{}
if err := json.Unmarshal(rs.Config, &data); err != nil {
return "", fmt.Errorf("unmarshal config: %w", err)
}

nextConfig, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("marshal config: %w", err)
}

return string(nextConfig), nil
}

// renderLauncher will render the launcher.js template which used as the entrypoint of the serverless function
func renderLauncher() (string, error) {
nextConfig, err := getNextConfig()
if err != nil {
return "", fmt.Errorf("get next config: %w", err)
}

tmpl, err := template.New("launcher").Parse(launcherTemplate)
if err != nil {
return "", fmt.Errorf("parse launcher template: %w", err)
}

type renderLauncherTemplateContext struct {
NextConfig string
}

var launcher strings.Builder
err = tmpl.Execute(&launcher, renderLauncherTemplateContext{NextConfig: nextConfig})
if err != nil {
return "", fmt.Errorf("render launcher template: %w", err)
}

return launcher.String(), nil
}
24 changes: 24 additions & 0 deletions internal/nodejs/nextjs/launcher.js.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
process.chdir(__dirname);
process.env.NODE_ENV = 'production';
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = "next"
const NextServer = require('next/dist/server/next-server.js').default;
const nextServer = new NextServer({
conf: {{ .NextConfig }},
dir: '.',
minimalMode: true,
customServer: false,
});
const requestHandler = nextServer.getRequestHandler();
module.exports = async (req, res) => {
const { NodeNextRequest, NodeNextResponse} = require('next/dist/server/base-http/node');
req = new NodeNextRequest(req)
res = new NodeNextResponse(res)
try {
await requestHandler(req, res);
} catch (err) {
console.error(err);
process.exit(1);
}
};
Loading

0 comments on commit 5a88021

Please sign in to comment.