无主之地2配置高吗|看真人裸体BBBBB|秋草莓丝瓜黄瓜榴莲色多多|真人強奷112分钟|精品一卡2卡3卡四卡新区|日本成人深夜苍井空|八十年代动画片

網易首頁 > 網易號 > 正文 申請入駐

為什么不能用同一種語言寫前端和后端?MoonBit 給出了另一種解法

0
分享至


前后端分離幾乎已經成為 Web 應用開發的標準模式,但它也帶來了不少工程負擔。

開發者通常需要在前端和后端分別定義領域模型。隨著項目演進,這兩套模型幾乎必然會在某個時間點出現不一致。除此之外,團隊還需要處理數據序列化、跨語言類型約束,以及前后端拆分后帶來的開發、調試和協作割裂感。

于是,一個很自然的問題出現了:

為什么不能用同一種語言開發前端和后端,并盡可能共享代碼?

問題在于,前端和后端運行在完全不同的環境中,對技術棧的要求也不一樣。

在后端,開發者通常更關心低延遲、可預測的內存使用和原始計算能力。而在瀏覽器端,應用需要很好地融入 JavaScript 生態,并盡可能控制包體積。這些約束并不一致,因此很難用單一編譯目標同時滿足兩端需求。

MoonBit 嘗試提供另一種思路: 支持多種編譯目標,讓系統的不同部分運行在最適合自己的環境中。

目前,MoonBit 可以面向不同目標進行編譯:

  • Native :使用 Perceus 內存管理算法,避免 GC 停頓并降低內存開銷,適合性能敏感的后端服務。

  • JavaScript :可以直接運行在 JS 運行時上,便于與 DOM 和瀏覽器 API 交互,不需要額外的中間層。

  • WebAssembly :適合計算密集型前端邏輯,通??梢詭砀玫男阅芎透〉陌w積。

這意味著,開發者不需要強行把所有代碼塞進同一個目標,而是可以根據不同模塊的運行環境選擇合適的編譯目標,同時保持核心邏輯共享。

當然,這也帶來了新的問題:這樣的項目應該如何組織?代碼如何跨目標共享?又如何避免整個開發流程變得混亂?

本文將通過一個簡單的 Todo 應用,看看 MoonBit 的工具鏈如何在實踐中處理這些問題。

Workspace 與 supported_targets

MoonBit 0.9.0 工具鏈最初引入了 moon.work 配置。 moon.work 表示一個 workspace,可以聲明多個需要一起管理的 MoonBit 模塊。

例如,假設 projects 文件夾中包含 module1 和 module2 兩個模塊:

projects
+- subdir
| `- module1
+- module2
`- moon.work

那么, projects 下的 moon.work 文件可以這樣聲明它所管理的模塊路徑:

members = [
"./subdir/module1",
"./module2",
]

當運行 moon check 或 moon build 等操作時,構建系統會嘗試定位當前 workspace,并觸發對其中每個項目的檢查和構建。

如果多個模塊之間存在依賴關系,構建系統會忽略依賴的版本號,優先從同一個 workspace 內的源碼構建依賴。

這對于偏好 monorepo 風格的團隊更加友好,也有助于當前 AI 輔助開發場景下的跨項目協作。

此外,工具鏈還在配置文件中引入了 supported_targets = "..." 約束。社區中的包可能只支持特定后端。例如:

  • bikallem/webapi 只支持 JS/Wasm;

  • moonbit-community/sqlite3 只支持 Native。

理想情況下,構建系統應該知道每個包支持哪些目標,并在條件不滿足時給出可讀的錯誤信息,而不是拋出一堆來自包內部實現的編譯錯誤。

mooncakes.io 生態

除了工具鏈本身的支持,前后端一體化應用也依賴一組與 Web 開發相關的基礎庫。

MoonBit 的包管理中心已經逐步覆蓋這些能力,包括異步編程、前端 UI、后端服務、數據庫訪問等常見場景。

例如:

  • async :MoonBit 的基礎異步庫,提供 fs 、 process 、 http 等 API。

  • rabbita :前身是 rabbit-TEA,一個函數式、單向數據流前端框架,已經用于構建 mooncakes.io 和 MoonBit 官方網站。

  • sqlite3 :輕量級的 SQLite3 底層綁定。

  • webapi :類型安全的瀏覽器 API 綁定,由 WebIDL 自動生成,覆蓋范圍較廣。

  • luna :一個基于 signal 的響應式 UI 庫,采用 islands 架構。

  • mocket :一個支持 JavaScript 和 Native 后端的 Web 后端庫。

其中, rabbita 、 async 、 sqlite3 等庫都有 MoonBit 團隊參與維護,其接口設計也更符合 MoonBit 的最佳實踐。

可以看到,MoonBit 前端應用已經進入生產環境,后端開發用例也在逐步演進。

下面的示例中,我們將選擇其中一些庫來構建一個簡單的 Todo 應用,用同一種語言實現前端和后端邏輯,并盡可能共享代碼。

前后端一體化實踐

我們從一個最小項目開始。

為了同時支持前端和后端,并盡可能共享代碼,項目被拆分成三個模塊:

isomorphic-template
├── backend
├── frontend
└── shared

這種劃分是為了解決一個實際問題:前端和后端需要運行在不同環境中,但我們仍然希望它們盡可能使用同一套業務邏輯。

接下來,我們先從 shared 模塊開始。

共享代碼與領域模型

在這個項目中,前后端共同使用的數據結構都放在 shared 模塊中:

// in isomorphic-template/shared/data.mbt
using @json { traitFromJson }


pub(all) structTodo {
id : Id
content : String
} derive(Debug, Eq, ToJson, FromJson)

這個定義本身并不復雜,但它有一個重要作用: 前端和后端直接使用同一個 Todo 類型。

derive(ToJson, FromJson) 會自動為 Todo 生成 ToJson 和 FromJson trait 的實現。

當后端返回響應時, json_value(...) 會依賴 ToJson 將 Todo 轉成 JSON;當讀取請求體時, event.json() 會依賴 FromJson 將 JSON 還原成 Todo 。

這樣一來,前后端共享的不只是字段定義,還包括圍繞這個類型生成的序列化規則。

只要 Todo 定義保持一致,API 傳輸的數據格式也會保持一致,不需要分別在前端和后端維護轉換邏輯。

有了這層基礎之后,我們再來看后端實現。

用 MoonBit 編寫 HTTP 服務和數據庫層

在后端,我們使用 crescent 中的 Mocket 構建 HTTP 服務,并使用 sqlite3 存儲數據。

整體風格與常見 Web 框架沒有太大差異,邏輯主要圍繞路由組織:

using @crescent { type HttpResponse, type Mocket }


fn main {
...
let app = Mocket()
app.get("/api/todo", _ => HttpResponse::ok().json_value(store.list_todo()))
app.post("/api/todo", event => {
let todo : @shared.Todo = event.json()
HttpResponse::created().json_value(store.create_todo(todo))
})
app.delete("/api/todo/:id", event => {
store.delete_todo(event.require_param_int("id"))
HttpResponse::no_content()
})
}

這里最值得注意的是數據如何流動。

crescent 會在 API 上添加 FromJson 和 ToJson 的 trait 約束。請求體可以直接被解析為 @shared.Todo ,返回值也可以直接使用同一類型的集合。

整個過程中沒有引入額外的數據結構,也不需要手寫序列化邏輯。具體調用哪套序列化邏輯由靜態類型決定,不會帶來額外的運行時分發開銷。

數據庫層只提供數據讀寫能力。我們用一個簡單的 Store 對它進行封裝,讓 API 始終圍繞 shared 中定義的類型展開。

Store 本質上只是一個持有長期數據庫連接的結構體,并基于 sqlite3 提供的 Connection::prepare 方法封裝幾個 SQL 查詢:

struct Store {
conn : @sqlite3.Connection
}

pub fn Store::new(db_path : String) -> Store {
let conn = @sqlite3.Connection::open(db_path)
... // Omit the table initialization process
Store::{ conn }
}

fn Store::list_todo(store : Self) -> Array[@shared.Todo] raise {
let stmt = store.conn.prepare(
"SELECT id, content FROM todo ORDER BY id ASC",
)
letitems = []
while stmt.step() {
items.push({
id: stmt.column(index=0),
content: stmt.column_blob_as_string(index=1),
})
}
stmt.finalize()
items
}

fn Store::delete_todo(self : Self, id : @shared.Id) -> Unit raise {
self.conn
.prepare("DELETE FROM todo WHERE id = ?")
..bind(index=1, val=id)
..step_once()
.finalize()
}

由于 sqlite3 是相對底層的綁定,使用方式也比較直接。感興趣的讀者也可以嘗試社區中已經出現的 PostgreSQL 或 ORM 相關庫。

受篇幅限制,本文暫時使用這個簡單封裝。

用 MoonBit 編寫前端

前端部分,我們使用 Rabbita 作為 UI 框架。

Rabbita 是一個受 The Elm Architecture 啟發的 UI 庫,下文簡稱 TEA。

TEA 范式對現代 Web 前端狀態管理設計產生了很深的影響。我們不僅可以在 Redux 以及各種前端生態變體中看到它的影子,也能在瀏覽器之外的 UI 庫中看到類似范式,例如 Iced 和 BubbleTea。

TEA 將 UI 更新拆分為幾個核心部分:

  • Model 表示狀態;

  • Msg 表示事件;

  • update() 處理狀態變化;

  • view() 將狀態渲染為界面。

這種模式的優勢在于,狀態變化之間的關系非常清晰。結合 MoonBit 的模式匹配和類型系統,前端邏輯可以自然地圍繞類型展開,也更便于 AI 輔助生成和修改。

在這個 Todo 應用中,前端的核心狀態是從后端加載的一組 Todo 項:

structModel {
todos : Vector[@shared.Todo]
}


enumMsg {
Refresh
GotRefreshed(Result[Vector[@shared.Todo], Error])
Delete(@shared.Id)
...
}

接下來, update() 負責將消息轉換為狀態變化,并在必要時返回一個 Cmd ,交給框架執行副作用:

fn update(emit : Emit[Msg], msg : Msg, model : Model) -> (Cmd, Model) {
match msg {
// Request torefresh todos
Refresh => {
let cmd = @http.get("/api/todo").expect_json(r => emit(GotRefreshed(r)))
(cmd, model)
}
// Todo request succeeded
GotRefreshed(Ok(todos)) => (none, { ..model, todos })
// Todo request failed
GotRefreshed(Err(_)) => (none, model)
Delete(id) => {
let cmd = @http.delete("/api/todo/\{id}")
.expect_empty(r => emit(GotDeleted(r)))
(cmd, { ..model, todos: model.todos.filter(x => x.id != id) })
}
GotDeleted(_) => (none, model)
...
}
}

這段代碼展示了 Rabbita 的核心工作流:

Refresh 發起請求,從 /api/todo 加載 todo 列表;請求結果隨后通過 GotRefreshed 回到 update() ;如果請求成功,就更新 model.todos 。

刪除時, Delete(id) 會生成一個 DELETE 請求,同時先從本地列表中移除對應條目。這是一種常見的樂觀更新方式,可以讓界面響應更及時。

界面渲染則由 view() 完成。它根據當前 todos 生成列表,并為每個按鈕綁定刪除消息:

fn view(emit : Emit[Msg], model : Model) -> Html {
let { todos, .. } = model
if todos.is_empty() {
p("No todos yet")
} else {
ul <| todos.map(todo => {
li <| [
p(todo.content),
button(on_click=emit(Delete(todo.id)), "delete")
]
})
}
}

到這里,前端邏輯同樣圍繞同一個 Todo 類型展開:

后端返回 Todo ,前端接收 Todo ,UI 渲染和事件處理也基于 Todo 。

這正是 shared 模塊在前后端一體化開發中的價值。

總結

本文通過一個簡單的 Todo 應用,介紹了如何用 MoonBit 編寫前后端一體化應用。

這個示例的重點并不在 Todo 應用本身,而在于: 前端和后端如何在保持運行環境分離的同時,共享同一套業務模型。

通過 shared 模塊,數據結構、序列化規則和校驗邏輯可以被集中定義。通過多編譯目標,后端可以運行在 Native 環境中,前端則可以編譯到 JavaScript 或 WebAssembly,從而讓兩端分別適配自己的運行場景。

這與傳統的服務端模板渲染不同。模板渲染通常以服務端動態生成頁面為中心。但隨著應用不斷增長,搜索、聯動表單、前端即時響應、開放 API 等需求會逐漸把邏輯分散到不同地方。同一條業務規則可能出現在多個位置,從而增加前后端不一致的風險。

MoonBit 的全棧工作流嘗試在前后端分離的基礎上減少這種割裂:它讓前端和后端共享核心業務定義,同時保留各自運行環境的優勢。

如何進一步打磨這套工作流、降低工程復雜度,并更充分地釋放 MoonBit 多目標編譯能力,仍然值得繼續探索。

我們也歡迎感興趣的讀者加入 MoonBit 社區。

MoonBit 官網

https://moonbitlang.cn

mooncakes.io

https://mooncakes.io

使用 WASM / JS 混合編譯目標的真實社區項目

https://github.com/CAIMEOX/symweb

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
菲利克斯:我沒義務傳球給C羅!葡國腳表態句句誅心,球王遭拋棄

菲利克斯:我沒義務傳球給C羅!葡國腳表態句句誅心,球王遭拋棄

林雁飛
2026-06-23 13:03:27
全球出現致命詭異趨勢:看似落后的印度,或將成為全世界最大隱患

全球出現致命詭異趨勢:看似落后的印度,或將成為全世界最大隱患

蜉蝣說
2026-06-23 16:40:06
雄鹿出全新15人名單!名記透露希羅或再換隊:特納庫茲馬還在詢價

雄鹿出全新15人名單!名記透露希羅或再換隊:特納庫茲馬還在詢價

顏小白的籃球夢
2026-06-23 19:01:44
一夜之間上萬斤粽子下架,不冷凍不重做,最后流向哪了呢?

一夜之間上萬斤粽子下架,不冷凍不重做,最后流向哪了呢?

小談食刻美食
2026-06-23 07:42:18
快訊!俄羅斯杜布納航天通信中心被炸!

快訊!俄羅斯杜布納航天通信中心被炸!

故事終將光明磊落
2026-06-23 11:04:37
原來他倆已離婚9年,一直共同撫養兒女,如今孩子一個比一個爭氣

原來他倆已離婚9年,一直共同撫養兒女,如今孩子一個比一個爭氣

以茶帶書
2026-06-22 17:41:20
貝克漢姆14歲的女兒小七怎么如此成熟了,好像少婦

貝克漢姆14歲的女兒小七怎么如此成熟了,好像少婦

西樓知趣雜談
2026-06-13 19:52:21
皇馬收到明確底價:想簽下邁克爾?奧利塞,必須掏出2.22億歐元!

皇馬收到明確底價:想簽下邁克爾?奧利塞,必須掏出2.22億歐元!

夜白侃球
2026-06-22 22:14:51
陳吉寧分別會見法國賽諾菲集團全球首席執行官葛麗鶴、英國匯豐集團主席聶智恒

陳吉寧分別會見法國賽諾菲集團全球首席執行官葛麗鶴、英國匯豐集團主席聶智恒

界面新聞
2026-06-23 19:41:38
難以置信,北京協和證實:40歲后男性最優運動,并非跑步擼鐵

難以置信,北京協和證實:40歲后男性最優運動,并非跑步擼鐵

華庭講美食
2026-06-21 15:26:10
陳小春演唱會,退票方案公布!粉絲不滿

陳小春演唱會,退票方案公布!粉絲不滿

南方都市報
2026-06-23 09:35:23
楊瀚森今夜首發!中國男籃VS荷蘭,先發5人出爐:賀希寧領銜防線

楊瀚森今夜首發!中國男籃VS荷蘭,先發5人出爐:賀希寧領銜防線

足球評論大家談
2026-06-23 19:29:57
中國科學家意外證實:沙漠中太陽能電池板可以喚醒荒漠生態系統

中國科學家意外證實:沙漠中太陽能電池板可以喚醒荒漠生態系統

小祁談歷史
2026-06-23 08:27:33
遺憾!上海一體彩站尋找中獎者7年,用盡方法都聯系不上,老店主:希望把這筆錢給到他....

遺憾!上海一體彩站尋找中獎者7年,用盡方法都聯系不上,老店主:希望把這筆錢給到他....

北青網-北京青年報
2026-06-23 13:43:18
一個殘酷真相:再過三年,再大牌的明星,也可能徹底無戲可拍

一個殘酷真相:再過三年,再大牌的明星,也可能徹底無戲可拍

一盅情懷
2026-06-23 13:34:28
保持冷靜,哈蘭德:法國大概會贏我們,他們可能會奪冠

保持冷靜,哈蘭德:法國大概會贏我們,他們可能會奪冠

懂球帝
2026-06-23 14:27:22
全新寶馬X5要來了,或6月30日首發,國產依舊加長

全新寶馬X5要來了,或6月30日首發,國產依舊加長

汽車公告板
2026-06-23 17:47:34
大放狠話!一國黨黨魁要求承認“臺灣是獨立國家”。澳官媒:瘋了,華人:不能投票給她

大放狠話!一國黨黨魁要求承認“臺灣是獨立國家”。澳官媒:瘋了,華人:不能投票給她

澳洲紅領巾
2026-06-23 14:31:46
10余省份公布高考查分時間

10余省份公布高考查分時間

新京報
2026-06-09 16:56:16
日本球迷亮出旭日旗,鈞正平:沾滿侵略血污的“戰犯旗”,被國際足聯明令禁止,世界杯賽場不是軍國主義的招魂地

日本球迷亮出旭日旗,鈞正平:沾滿侵略血污的“戰犯旗”,被國際足聯明令禁止,世界杯賽場不是軍國主義的招魂地

揚子晚報
2026-06-22 22:31:27
2026-06-23 20:47:00
開源中國 incentive-icons
開源中國
每天為開發者推送最新技術資訊
7823文章數 34546關注度
往期回顧 全部

科技要聞

48名中國開發者聯名舉報蘋果

頭條要聞

17歲女孩被閨蜜持刀刺成重傷對方自殺身亡 警方已撤案

頭條要聞

17歲女孩被閨蜜持刀刺成重傷對方自殺身亡 警方已撤案

體育要聞

揚尼斯去了邁阿密:凱爾特人怎么辦?

娛樂要聞

內娛95后頂流格局發生潛移默化的變化

財經要聞

屋頂光伏度苦夏

汽車要聞

華為智駕ADS限時優惠月底結束 7月1日前下訂立省3000元

態度原創

旅游
藝術
時尚
手機
軍事航空

旅游要聞

賽事引流、文化破圈 端午假期“熱氣騰騰”

藝術要聞

蘇軾醉酒后寫的草書,比張旭、懷素境界還高

除了瑪麗珍、薄底鞋,今年最火的鞋子就是它了

手機要聞

realme P4x 4G手機海外發布:8000mAh電池,6.8寸720P高刷屏

軍事要聞

以色列總理、國防部長和國防軍總參謀長發表聯合聲明

無障礙瀏覽 進入關懷版