櫻白色的喵窩quq

又一個新的Blog的技術棧實驗 (Elysia + Solidjs)

quqCreated on
quqBlogquqBlogProgrammingElysiaSolid

0x1 前言

雖然我的 Blog 沒甚麼內容,但技術棧確實換了好多遍了,從以前 Hexo 到 NextJs 到 Astro/Solidjs,還有一些像是 zola 和 dioxus 的廢案。不過前端的技術棧天天換倒是 DOM 從來沒變過,所以其實也沒甚麼特別好說的。但是為甚麼是 Elysia 和 Solid 呢,因為 Elysia 的圖標好看,為甚麼是 Solid 呢,因為我上個用的 SolidJS 可以直接搬過來而且性能好像很好。那為甚麼要基於這些好像沒甚麼意義理由做一個框架呢,因為人活着得有點感性思維吧,看蘋果華為靠說故事賣電子垃圾賺了多少錢,天天基於各種指標糾結哪個更好就不是人了。

其實我知道 Elysia 還是因為最近 VTuber Logo 很火,我看了一眼怎麼有個名字這麼二次元的框架,點進去一看圖標也很二次元,那我肯定得想辦法用上的對吧(。不過框架搓出來後又有一個?kawaii=true 大家都二次元起來了((有機會我也要搓一個 Logo 給自己 Blog

0x2 TL;DR

https://github.com/paletteOvO/elysia-solidjs

Blog 上實際配置了更多東西,不過基本上都是客製化而 repo 裡的是最小化實現所以就沒加進去了。

0x3 目標

目標是想要 Elysia 後端 + Solid 前端,而且要 Elysia 直接提供 Solid 的各種組件,那很多現有的工具就用不上了。同時因為是 Blog, SEO 是必須考慮的, 那作為 SPA-First 框架的 Solid 也得弄上 SSR 了(雖然 Google 似乎會執行 JavaScript, 但姑且還是做一下 SSR 吧,體驗也更好一點)。那上 SSR 吧...好像製造了更多的問題(比如 Hydration,比如我要怎麼自己搓 server side renderer...我還得做 MPA,因為這還是 server side routing 的,當然這些最後都解決了,某程度上!(總之只有要愛就沒問題了!

0x4 配置 ElysiaJS

參考官網 Quick Start

因為是普通的 ElysiaJS 後端所以沒甚麼特別好說的,不過因為要用到 Solid 所以官網上官方的啟動指令很快就用不上了

bun create elysia app

0x4 配置 SolidJS

參考官網 Getting Started
真的只能參考一下,因為要用 ElysiaJS, 所以這裡的啟動指令也用不上了。

但首先還是要安裝 SolidJS 和 vite,如果想直接用 tsconfig.json 裡的 path alias 的話需要加個 typescript 插件
(雖然 Bun 可以直接執行 ts 和 tsx,但 solidjs 要編譯所以 vite 這一步是繞不過去的)

bun add vite vite-plugin-solid

配置 vite.config.js

import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

// import typescript from "@rollup/plugin-typescript";

export default defineConfig({
   build: {
      ssr: "src/index.ts",
      emptyOutDir: true,
      target: "esnext",
      rollupOptions: {
         plugins: [
            // typescript()
         ],
      },
   },
   plugins: [
      solidPlugin({
         ssr: true,
      }),
   ],
});

這樣 vite build 就能編譯 solidjs 的 tsx,然後只需要讓 elysia 給前端返回編譯過的 tsx 就好了。

但是這裡有一個問題,solidjs 是 client side 的 script,在伺服器上編譯打包了的是沒法直接傳給瀏覽器用的,所以我們就需要做 ssr 和 hydrate。

0x5 SSR

那要怎麼做 SSR 呢,solidjs 非常友好提供了 renderToString,只要在 elysia 的 index.ts 中直接提供就好

import { Elysia } from "elysia";
import IndexPage from "./pages/index";
import { renderToString } from "solid-js/web";

const app = new Elysia()
   .get("/", ({ set }) => {
      set.headers["content-type"] = "text/html";
      return renderToString(() => IndexPage());
   })
   .listen(3000);

console.log(
   `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);

0x6 Hydration

ssr 後瀏覽器拿到的只是一個靜態的 html 檔案,還需要進行 hydration 才能有交互事件。但是 browser 需要拿到一個另外編譯的客戶端專用的 js。在 solid-ssr 中官方是 rollup+babel 編譯的,但 vite 只能有一個 entry,而且他們需要的配置也不太一樣,所以只能另外再寫一個 vite.config.ts 來編譯客戶端的腳本。

# vite.client.config.ts
import { defineConfig } from "vite";
import solidPlugin from "vite-plugin-solid";

export default defineConfig({
   build: {
      ssr: false,
      emptyOutDir: false,
      rollupOptions: {
         input: "src/entry.ts",
         output: {
            dir: "dist",
            format: "es",
            entryFileNames: "entry.js",
         },
         plugins: [],
      },
   },
   plugins: [
      solidPlugin({
         ssr: true,
      }),
   ],
});
# entry.ts
import { hydrate } from "solid-js/web";
import App from "./pages/index";
hydrate(() => App()), document.body);
# pages/index.ts

import { createSignal, onMount } from "solid-js";
import { HydrationScript } from "solid-js/web";

export default () => {
   const [count, setCount] = createSignal(0);
   onMount(() => {
      console.log("Hello world");
   });
   return (
      <>
         <HydrationScript />
         <div>{count()}</div>
         <button onClick={() => setCount(count() + 1)}>Increment</button>
         <script defer src="/entry.js"></script>
      </>
   );
};
# index.ts

import { Elysia } from "elysia";
import IndexPage from "./pages/index";
import { renderToString } from "solid-js/web";

const app = new Elysia()
.get("/", ({ set }) => {
   set.headers["Content-Type"] = "text/html";
   return "<!DOCTYPE html>" + renderToString(() => IndexPage());
})
.get("/entry.js", ({ set }) => {
   set.headers["Content-Type"] = "application/javascript";
   return Bun.file("./dist/entry.js");
})
.listen(3000);

console.log(
   `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);

到此你就得到了一個簡單的 Elysia + Solid 的技術棧了

0x7 MPA

待續

0x8 URA

blog 已經進化到 Universal Rendering App 了,不過我還懶得寫(