Skip to content

Commit 812f6bd

Browse files
authored
Merge pull request #118 from qianmoQ/dev-26.4.0
Refactor documentation layout and add blog module
2 parents 699b977 + 76ddd64 commit 812f6bd

22 files changed

Lines changed: 630 additions & 56 deletions

docs/src/App.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
<SiteHeader/>
44
<main class="flex-1">
55
<RouterView v-slot="{ Component, route }">
6-
<DocLayout v-if="route.meta.doc">
6+
<DocLayout v-if="route.meta.doc && route.meta.docType === 'release'">
77
<component :is="Component"/>
88
</DocLayout>
9+
<BlogLayout v-else-if="route.meta.doc && route.meta.docType === 'blog'">
10+
<component :is="Component"/>
11+
</BlogLayout>
912
<component :is="Component" v-else/>
1013
</RouterView>
1114
</main>
@@ -17,4 +20,5 @@
1720
import SiteHeader from './components/SiteHeader.vue'
1821
import SiteFooter from './components/SiteFooter.vue'
1922
import DocLayout from './layouts/DocLayout.vue'
23+
import BlogLayout from './layouts/BlogLayout.vue'
2024
</script>

docs/src/components/SiteFooter.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<div class="flex items-center gap-6">
1010
<a class="hover:text-brand-600 dark:hover:text-brand-400 transition-colors" href="https://github.com/devlive-community/codeforge" target="_blank" rel="noopener">GitHub</a>
1111
<RouterLink class="hover:text-brand-600 dark:hover:text-brand-400 transition-colors" to="/download">{{ t('footer.download') }}</RouterLink>
12+
<RouterLink class="hover:text-brand-600 dark:hover:text-brand-400 transition-colors" to="/blog">{{ t('footer.blog') }}</RouterLink>
1213
<RouterLink class="hover:text-brand-600 dark:hover:text-brand-400 transition-colors" to="/release">{{ t('footer.releases') }}</RouterLink>
1314
</div>
1415
</div>

docs/src/components/SiteHeader.vue

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,20 @@
88

99
<div class="hidden sm:flex items-center gap-1 text-[15px]">
1010
<RouterLink to="/download" class="px-3 py-2 rounded-lg text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 transition-colors" active-class="!text-brand-600 dark:!text-brand-400">{{ t('nav.download') }}</RouterLink>
11-
<RouterLink to="/release" class="px-3 py-2 rounded-lg text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 transition-colors" active-class="!text-brand-600 dark:!text-brand-400">{{ t('nav.releases') }}</RouterLink>
11+
<RouterLink to="/blog"
12+
:class="[
13+
'px-3 py-2 rounded-lg text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 transition-colors',
14+
route.path.startsWith('/blog') && '!text-brand-600 dark:!text-brand-400'
15+
]">
16+
{{ t('nav.blog') }}
17+
</RouterLink>
18+
<RouterLink to="/release"
19+
:class="[
20+
'px-3 py-2 rounded-lg text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white hover:bg-slate-100 dark:hover:bg-slate-800/60 transition-colors',
21+
route.path.startsWith('/release') && '!text-brand-600 dark:!text-brand-400'
22+
]">
23+
{{ t('nav.releases') }}
24+
</RouterLink>
1225
</div>
1326

1427
<div class="ml-auto flex items-center gap-1">
@@ -32,8 +45,10 @@
3245

3346
<script setup lang="ts">
3447
import {useI18n} from 'vue-i18n'
48+
import {useRoute} from 'vue-router'
3549
import ThemeToggle from './ThemeToggle.vue'
3650
import LangToggle from './LangToggle.vue'
3751
3852
const {t} = useI18n()
53+
const route = useRoute()
3954
</script>

docs/src/content/blog/welcome.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
title: 我用 Tauri 2 做了一个能跑 30 多种语言的代码运行器,聊聊插件化架构是怎么扛住的
3+
date: 2026-06-06
4+
author: CodeForge Team
5+
description: 我想要的东西其实很简单:打开就能写、写完一键就能跑、还能顺手把跑出来的 JSON 看明白。最好是个桌面应用,本地、快、不依赖网络。
6+
tags: ['公告', '开始']
7+
---
8+
9+
> 这是一篇实现向的文章。如果你只想看产品,项目在这里:<https://github.com/devlive-community/codeforge>。如果你也在用 Tauri 折腾桌面工具,下面这些架构上的取舍和坑,也许能帮你少走点弯路。
10+
11+
## 起因:我想要一个「代码草稿本」,但没找到顺手的
12+
13+
事情的起点很朴素。
14+
15+
我经常需要快速验证一小段代码——可能是一段 Python 的正则、一个 Go 的并发写法、一段 Rust 的所有权实验。问题是,为了跑这几行临时代码,要么得新建一个项目、配一遍环境,要么开浏览器找在线 playground,但在线的那些通常只支持一两种语言,还动不动连不上。
16+
17+
我想要的东西其实很简单:**打开就能写、写完一键就能跑、还能顺手把跑出来的 JSON 看明白**。最好是个桌面应用,本地、快、不依赖网络。
18+
19+
找了一圈没有完全合手的,于是我自己做了一个,叫 CodeForge。做到现在它支持 30 多种语言的运行,从 Python、Node、Go、Rust 到 Swift、Haskell,甚至 AppleScript。这篇文章我想重点聊的不是功能清单,而是支撑这一切的那个东西——**插件化的语言系统**,以及它在工程上是怎么落地的。
20+
21+
## 第一个决定:为什么是 Tauri 2,而不是 Electron
22+
23+
做桌面应用,绕不开的第一个问题就是用什么壳。
24+
25+
Electron 当然是最省心的选择,生态成熟、文档全、遇到问题基本都能搜到答案。我一开始也确实往那个方向想过。但越想越觉得它有两个我不愿意接受的代价:**包体积****内存占用**。一个定位「随手打开的代码草稿本」,如果装完一百多兆、空载就吃掉几百兆内存,那它从第一秒起就背离了我做它的初衷——那种「轻、快、即开即用」的手感,本身就是这个产品的核心体验。
26+
27+
Tauri 2 用系统自带的 WebView 渲染前端,后端是 Rust,最终产物可以做得很小,常驻内存也低得多。对一个主打轻量的工具来说,这个差别不是「优化项」,而是「成立与否」的问题。
28+
29+
代价也是有的,我得诚实地说:
30+
31+
- WebView 在不同平台上行为不完全一致(macOS 的 WKWebView、Windows 的 WebView2、Linux 的 WebKitGTK),偶尔要写平台分支;
32+
- Rust 的学习曲线比 Node 陡,前期开发明显更慢;
33+
- 遇到问题时能搜到的现成答案比 Electron 少很多,不少坑得自己啃文档和 issue。
34+
35+
但对这个项目来说,这些代价换来的轻量和性能,我认为是值的。最终的技术栈是:前端 Vue 3 + TypeScript + CodeMirror 6,后端 Rust + Tauri 2,执行历史、AI 对话、代码片段、应用配置统一进 SQLite。
36+
37+
## 核心难题:30 多种语言,怎么才能不写成 30 个 if-else
38+
39+
支持一种语言的运行很简单:找到解释器/编译器,拼一条命令,起个子进程,把输出抓回来。
40+
41+
但当语言数量往上涨,这种天真的写法会迅速崩坏成一坨巨大的分支判断——「如果是 Python 就这样、如果是 Go 就那样、如果是 Rust 还要先编译再运行……」。每加一种语言都要回头改核心逻辑,每一种语言的特殊性都在污染主流程。这是典型的、会随规模膨胀而失控的设计。
42+
43+
所以从一开始我就把它设计成**插件化**的。但我想强调的是它的灵魂不在「插件」这个词,而在于——**它是配置驱动的**。核心引擎不认识任何具体语言,它只认识一份描述「这门语言该怎么跑」的配置;绝大多数通用逻辑(拼命令、起进程、抓输出、清理)都写在统一的地方,而每个语言插件本身薄得惊人。
44+
45+
### 一个语言插件,薄到什么程度
46+
47+
落到 Rust 里,所有语言实现同一个 trait:
48+
49+
```rust
50+
pub trait LanguagePlugin: Send + Sync {
51+
fn get_language_name(&self) -> &'static str; // 显示名,如 "Python 3"
52+
fn get_language_key(&self) -> &'static str; // 唯一键,如 "python3"
53+
fn get_file_extension(&self) -> String; // 扩展名
54+
fn get_version_args(&self) -> Vec<&'static str>; // 版本探测参数
55+
fn get_path_command(&self) -> String; // 探测工具链是否可用
56+
fn get_default_config(&self) -> PluginConfig; // 默认配置(核心)
57+
fn get_default_command(&self) -> String;
58+
// …还有一组带默认实现的钩子,下面会讲
59+
}
60+
```
61+
62+
真正承载「这门语言怎么跑」的,是它返回的那份 `PluginConfig`
63+
64+
```rust
65+
pub struct PluginConfig {
66+
pub enabled: bool,
67+
pub extension: String,
68+
pub language: String,
69+
pub run_command: Option<String>, // 命令模板,如 "python3 $filename"
70+
pub before_compile: Option<String>, // 运行前的准备命令
71+
pub after_compile: Option<String>, // 运行后的清理命令
72+
pub template: Option<String>, // 新建文件时的初始模板
73+
pub timeout: Option<u64>, // 超时(默认 30s)
74+
pub execute_home: Option<String>, // 自定义工具链路径
75+
//
76+
}
77+
```
78+
79+
于是一个解释型语言的插件,几乎就是「填一张表」。这是 Python 3 插件的全部核心——没有任何执行逻辑,只是声明「我叫什么、扩展名是啥、用 `python3 $filename` 跑」:
80+
81+
```rust
82+
fn get_default_config(&self) -> PluginConfig {
83+
PluginConfig {
84+
enabled: true,
85+
language: "python3".into(),
86+
extension: "py".into(),
87+
run_command: Some("python3 $filename".into()), // $filename 运行时被替换
88+
before_compile: None,
89+
after_compile: None,
90+
template: Some("# 在这里输入 Python 3 代码".into()),
91+
timeout: Some(30),
92+
//
93+
}
94+
}
95+
```
96+
97+
核心引擎拿到文件后,从配置里取出 `run_command`,把 `$filename` 替换成真实路径,剩下的——起进程、流式抓输出、算耗时——全是**与语言无关**的通用逻辑。加一门解释型语言,本质上就是再填一张这样的表。
98+
99+
### 编译型语言怎么塞进同一套抽象
100+
101+
解释型一步到位,麻烦的是 Rust、C/C++、Java 这类「先编译、再运行」的两步语言。我没有为它们单独造一套机制,而是用了两个朴素但好使的办法。
102+
103+
**第一招:用 shell 的 `&&` 把两步串成一条命令。** Rust 插件就是这么干的——它的运行命令实际展开成:
104+
105+
```text
106+
rustc /path/to/main.rs -o /tmp/main && /tmp/main
107+
```
108+
109+
编译和运行被 `&&` 连成一个整体交给 shell。编译失败,`&&` 短路,编译器的报错就成了本次运行的输出——而这恰恰是「验证代码」场景里用户最想看到的东西,根本不需要单独搞一套错误展示。跑完之后,`after_compile` 配了 `rm -f /tmp/main` 把产物清掉。Windows 上则换成 `cmd /c "rustc ... -o main.exe && main.exe"`,平台差异用 `#[cfg(target_os)]` 分支隔开。
110+
111+
**第二招:`before_compile` / `after_compile` 两个钩子。** Go 插件的 `before_compile``go mod init temp 2>/dev/null || true`——临时跑一段 Go 片段得先有个 module,这个钩子就替用户把它补上了,`|| true` 保证已经初始化过时不报错。运行命令本身用 `go run $filename`,一步编译加运行。
112+
113+
Java 我直接用了 `java $filename`:Java 11 起支持单文件源码模式,一条命令就能编译加运行,连 `javac` 那一步都省了;再挂一个 `after_compile: rm -f *.class` 兜底清理可能残留的字节码。
114+
115+
你看,编译型语言的全部「特殊性」,最后都收敛成了配置里的几个字符串字段,没有一行渗进核心引擎。
116+
117+
### 这套设计最大的红利:加语言常常不用写代码
118+
119+
因为「怎么跑一门语言」已经被完整地编码进了 `PluginConfig`(命令模板 + 编译前后钩子 + 扩展名 + 超时),所以新增语言很多时候根本不需要写 Rust——填一份配置就行。我把这个能力直接开放给了用户:内置插件之外,还有一类 `CustomPlugin`,启动时从用户配置里加载,只要语言键不和内置的冲突,用户就能纯靠填配置给 CodeForge 加一门新语言。配置驱动设计在这里兑现了它最大的价值。
120+
121+
## 运行时的几个真实工程细节
122+
123+
把代码跑起来,魔鬼都在细节里。几个我觉得值得一提的:
124+
125+
**工具链探测。** 用户机器上不一定装了对应工具链——没人会为了跑段 Haskell 恰好装了 GHC。所以每个插件都要给出探测自己的方式:`get_version_args` 提供版本参数(Python 是 `--version`、Java 是 `-version`、Go 是 `version`,各不相同),`get_path_command` 给出定位工具的命令——有些直接得很巧,比如 Python 用 `import sys; print(sys.executable)` 反查自己的解释器路径。探测不到就给一句明确的「未检测到 xxx」,而不是甩给用户一坨 `command not found`
126+
127+
**在配置里设环境变量。** `before_compile` 不只能跑准备命令,还能写 `export KEY=value`(Unix)或 `set KEY=value`(Windows),插件会解析它、跨平台地设进环境,还会帮你展开 `$PATH` / `%PATH%`。这样一些需要特定环境变量才能跑的语言,不用改代码、配置里写一行就解决了。
128+
129+
**多标签并发与流式输出。** 每次运行请求都带一个 `task_id`,用它做事件路由:Rust 端读到一段子进程输出就带着 `task_id` 发事件,前端按 `task_id` 把输出投递到对应的标签页。于是多个标签可以同时各跑各的,输出实时流式刷新、互不串台,而不是憋到进程结束才一次性吐出来。
130+
131+
**「成功但没输出」也是一种结果。** 有个小细节我自己挺得意:运行成功、但 stdout/stderr 都为空时,后执行钩子会填一个 `END-NO-OUTPUT` 的哨兵值。否则用户点了运行、界面一片空白,根本分不清是「跑成功了但本来就没输出」还是「卡住了」。一个哨兵值,把这两种状态明确区分开。
132+
133+
**每语言可配的超时。** 默认 30 秒,但写死在配置里、可按语言调整,避免一段死循环把应用拖住。
134+
135+
## 顺手做的一件事:把结构化数据「看明白」
136+
137+
跑代码经常会吐出 JSON,而一坨压扁的 JSON 是很难读的。
138+
139+
所以我顺手给 JSON / XML / YAML 做了两种可视化:可折叠的**层级树**,以及把字段关系画成卡片 + 连线的**关系图**。Markdown 会实时渲染预览(内嵌 HTML 用 DOMPurify 净化,防 XSS)。还有一个我自己很喜欢的小功能——识别到文件是 GitHub Actions 的 workflow 时,自动把它渲染成 **Jobs 依赖的 DAG 图**:触发事件、各个 Job、它们之间的依赖和每个 Job 的 Steps,一目了然,比对着 YAML 数缩进强多了。
140+
141+
## 写在最后
142+
143+
做到现在,CodeForge 大概是我「最想自己天天用」的一个项目——它解决的就是我自己每天都会遇到的麻烦。配置驱动的插件化在前期看起来有点「过度设计」,但当语言数量从 5 种涨到 30 多种时,它的价值才真正显现:核心一直很干净,加语言变成了一件愉快的、甚至能交给用户自己完成的事。
144+
145+
如果你对实现感兴趣,或者也想要这么一个本地的多语言代码草稿本,欢迎来看看、点个 star、提 issue:
146+
147+
**👉 <https://github.com/devlive-community/codeforge>**
148+
149+
技术栈:Vue 3 + TypeScript + CodeMirror 6(前端)· Rust + Tauri 2(后端)· SQLite(存储)· 配置驱动的插件化语言系统(架构)。
150+
151+
对了,**你最希望它支持哪种语言、或者最想要什么功能?** 评论区告诉我,下一个版本说不定就给你安排上。

docs/src/content/blogs.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// 技术博客:扫描 blog/*.md,自动生成博客列表与路由组件。
2+
3+
export interface BlogMeta {
4+
slug: string
5+
title: string
6+
date?: string
7+
author?: string
8+
description?: string
9+
tags?: string[]
10+
}
11+
12+
const slugFromPath = (p: string): string => (p.match(/([^/]+)\.md$/)?.[1]) ?? p
13+
14+
// 按日期降序排序
15+
const compareDateDesc = (a: string | undefined, b: string | undefined): number => {
16+
if (!a && !b) return 0
17+
if (!a) return 1
18+
if (!b) return -1
19+
return b.localeCompare(a)
20+
}
21+
22+
// 导入所有 markdown 文件
23+
const modules = import.meta.glob<any>('./blog/*.md', { eager: true })
24+
25+
const blogsData: BlogMeta[] = Object.entries(modules).map(([path, module]) => {
26+
const slug = slugFromPath(path)
27+
// 日期格式为 ISO 8601 字符串,提取 YYYY-MM-DD 部分
28+
const date = module.date
29+
const dateStr = typeof date === 'string' ? date.split('T')[0] : date
30+
return {
31+
slug,
32+
title: module.title || slug,
33+
date: dateStr,
34+
author: module.author,
35+
description: module.description,
36+
tags: module.tags || []
37+
}
38+
})
39+
40+
export const blogs: BlogMeta[] = blogsData.sort((a, b) => compareDateDesc(a.date, b.date))
41+
42+
// 路由:每篇博客一个静态路由,便于 vite-ssg 全量预渲染
43+
const componentLoaders = import.meta.glob('./blog/*.md')
44+
export const blogRoutes = Object.entries(componentLoaders).map(([path, loader]) => ({
45+
path: `/blog/${slugFromPath(path)}`,
46+
component: loader
47+
}))

docs/src/content/release/25.0.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
title: 25.0.0
3+
date: 2025-08-11
4+
description: 首个正式版本发布
35
---
46

57
我们激动地宣布 **CodeForge v25.0.0** 正式发布!这是 CodeForge 项目的首个正式版本,标志着这款轻量级、高性能桌面代码执行器正式与大家见面。

docs/src/content/release/25.0.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
title: 25.0.1
3+
date: 2025-08-18
4+
description: 首个版本的问题修复
35
---
46

57
CodeForge v25.0.1 正式发布!这是一个重要的功能更新版本,我们在短短几天内就为用户带来了令人振奋的新特性。本次更新重点加强了多语言支持,升级了编辑器体验,并优化了整体性能表现。

docs/src/content/release/25.0.2.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
title: 25.0.2
3+
date: 2025-08-25
4+
description: 性能优化和错误修复
35
---
46

57
CodeForge v25.0.2 隆重登场!本次更新是我们迄今为止最具雄心的版本,大幅扩展了编程语言支持范围,并对编辑器体验进行了全方位优化。从主流语言到小众语言,从界面美化到功能增强,我们致力于为每一位开发者提供更完善的代码执行环境。

docs/src/content/release/25.0.3.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
title: 25.0.3
3+
date: 2025-09-01
4+
description: UI 优化和用户体验改进
35
---
46

57
CodeForge v25.0.3 重磅发布!本次更新带来了突破性的 Web 技术栈支持和前所未有的编辑器个性化配置能力。我们不仅新增了 12 种编程语言和技术栈,更引入了革命性的 Web 渲染模式,让 CodeForge 真正成为一个全栈开发者的理想工具。

docs/src/content/release/25.0.4.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
---
22
title: 25.0.4
3+
date: 2025-10-09
4+
description: 稳定性提升和 Bug 修复
35
---
46

57
CodeForge v25.0.4 正式发布!本次更新专注于 Apple 生态系统的完善和函数式编程能力的增强。我们新增了 4 种重要编程语言,特别是 Objective-C 家族的加入,让 CodeForge 成为 iOS/macOS 开发者验证代码的得力工具。

0 commit comments

Comments
 (0)