当前位置: 首页 > news >正文

bun全栈开发尝鲜:用bun-react-template实现Markdown文章展示

文章目录

    • 引言
    • 初始化项目
      • 引入 highlight.js
      • highlight.js 生成的元素没有类名
      • 没看到 prose 类相关的样式
    • marked 支持 mermaid
    • 接入 eslint
    • 打包流程(并没有考虑部署上线)
    • 为所有页面添加统一的导航栏和页脚
    • 体验感受
    • 参考资料

引言

bun 是一个新兴的 JavaScript 运行时,旨在提供比 Node.js 更快的性能和更好的开发体验。本文是一个 bun 尝鲜项目,将介绍如何使用 bun-react-template 创建一个简单的全栈应用,实现 Markdown 文章展示功能。

根据官方文档安装,然后“编辑系统环境变量”找到“admin 的用户变量”的 Path 变量,把 bun 新增的路径改成C:\bun\.bun\bin(仅适用于我个人)。

项目 GitHub 传送门

本文 52pojie:https://www.52pojie.cn/thread-2034041-1-1.html

本文 CSDN:https://blog.csdn.net/hans774882968/article/details/148211959

本文 juejin:https://juejin.cn/post/7507888457705226303

作者:hans774882968以及hans774882968以及hans774882968

初始化项目

命令:bun init。注意,和 Vite 不同,bun 不会问你项目名,而 package.json 的 name 默认为“bun-react-template”,所以需要提前建好文件夹。另外,bun 自动安装依赖的时候连个进度条都没有,就在那卡着,确实很不友好。

给 Cursor 的 Prompt:

请帮我实现 markdown 展示功能。

后端:

接口返回值规范:Resp<T>: { code: number, msg: string, data: T }。比如对于 404 的情况,不返回 HTTP 404,而是返回{ code: 404, msg: 'not found', data: null }

GET 接口/allArticles。无入参,返回 JSON:Array<{ url: string, title: string }>

GET 接口/article/:aid,获取单篇文章。aid 单调递增,初始值为 114514,每次增加 10 到 20。返回 JSON:{ content: string, title: string }

为了实现方便,不使用 SQL,而是用 JSON 文件存储文章信息。该文件有 2 个字段,aid 和 title。获取单篇文章的接口读取该文件后,在项目根目录的/mds 文件夹下读取对应标题的 markdown 文件,作为返回值 JSON 的 content 字段。

前端:

首页:请求/allArticles,渲染出文章列表。每篇文章有一个 div,div 下有一个 a 标签。

具体文章:请求单篇文章的接口,接口返回 code: 404 则重定向到 404 页面,404 页面要告知用户,找不到的文章的 ID。否则渲染 markdown 文章为 HTML 并展示。markdown 文章的代码要用 highlight.js 实现高亮功能。

渲染 markdown 时注意防范 XSS。

修改 bun 项目配置,开发阶段前端运行在端口 5201,后端运行在端口 5202。如果 bun 项目的前后端默认跑在同一个端口,则都运行在 5201 端口。

默认端口为 3000,修改方式:

const server = serve({port: process.env.NODE_ENV === "production" ? 5202 : 5201,
});

Cursor 生成的代码基本可用,但需要修些小问题。

引入 highlight.js

AI 生成的引入 highlight.js 的代码不可用。查看文档,新版 marked 要这么引入:

// 配置 marked 使用 marked-highlight 和 highlight.js
marked.use(markedHighlight({emptyLangClass: "hljs",langPrefix: "hljs language-",highlight(code, lang, _info) {const language = hljs.getLanguage(lang) ? lang : "plaintext";return hljs.highlight(code, { language }).value;},})
);

highlight.js 生成的元素没有类名

sanitize-html 默认会过滤掉 spanclass 属性,导致highlight.js生成的样式类名丢失。

const sanitizedHtml = sanitizeHtml(marked(article.content), {allowedTags: sanitizeHtml.defaults.allowedTags.concat(["img"]),allowedAttributes: {...sanitizeHtml.defaults.allowedAttributes,code: ["class"],pre: ["class"],// 关键修复:允许 span 的 class 属性(hljs 生成的元素)span: ["class"],},
});

没看到 prose 类相关的样式

Tailwind CSS 4 不再需要tailwind.config.js,可直接在 CSS 文件加一行@pluginsrc\index.css新增:

@plugin "@tailwindcss/typography";

marked 支持 mermaid

效果:

在这里插入图片描述

询问 deepseek 或者看参考链接 2知道,需要先引入 mermaid:bun add mermaid。接着自定义一个 marked renderer 把 mermaid 代码包起来:

// Custom renderer for mermaid code blocks
const renderer: RendererObject = {code({ lang, text }) {if (lang === "mermaid") {return `<div class="mermaid">${text}</div>`;}return false; // use default rendering},
};

然后调用 mermaid 包渲染出 svg:

// Initialize mermaid
useEffect(() => {mermaid.initialize({startOnLoad: false,theme: "dark",securityLevel: "strict",});
}, []);// TODO: 错误处理
// Render mermaid diagrams after content loads
useEffect(() => {if (!articleRef.current || !htmlContent) {return;}const mermaidElements =articleRef.current.querySelectorAll<HTMLElement>(".mermaid");if (mermaidElements.length > 0) {try {mermaid.run({nodes: mermaidElements,});} catch (error) {console.error("Mermaid rendering error:", error);}}
}, [htmlContent]);

接入 eslint

找了个 Vite + React 的 eslint 配置文件。

bun add -D eslint globals
@eslint/js typescript-eslint eslint-plugin-react-hooks eslint-plugin-react-refresh

然后配置:"lint": "eslint ."。使用:bun lint --fix

打包流程(并没有考虑部署上线)

我确实不喜欢 bun 项目模板的项目结构,所以调整成了这样:

src:frontend:components:Article.tsxArticleList.tsxNotFound.tsxApp.tsxindex.cssspa.tsxbackend:beIndex.ts

说实话,bun 打包的问题确实困扰了我许久。bun 项目在 GitHub 有那么多 star,但是 bun 给的模板项目也好,官方文档也好,互联网上也好,这方面的资料都特别少。但通过查官方文档、问 AI 等手段,我感觉,bun 的前后端应该是需要不同命令打包的。打包前端,只需要用 bun 模板项目的build.ts脚本即可:bun run buildbun run build.ts(前者会自动找到后者)。但打包后端以后,总是跑不起来。我排查许久,才发现,原因是,打包过程会将

import index from "./index.html";

转为

var src_default = "./index-12n45nbf.html";

但这方面资料太少,我确实不知道怎么阻止这个动作的发生。接下来,我又尝试了bun build命令(注:和bun run build.ts是两个东西,这个只能用来打包前端)的--external参数,发现:如果指定index.html的路径为../index.html,那么指定这个参数无效;但如果指定路径为@/index.html,那么这个参数就能把 import 语句变成import M from"@/index.html";。from 后面没有空格是因为我给bun build指定了--production参数。

于是我决定,指定路径为@/index.html,将beIndex.ts打包进dist/backend,这样无论是在 src 下还是 dist 下,beIndex.ts都可以通过统一的../index.html找到 HTML 文件。然后让 deepseek 写一个脚本进行字符串替换:

// fix-html-import.ts
import { readFileSync, writeFileSync } from "fs";
import { join } from "path";const PROJECT_NAME = "bun-markdown-display";// Usage: 项目根目录 bun build:be2
function main() {const distFile = join(__dirname, "..", "dist", "backend", "beIndex.js");const code = readFileSync(distFile, "utf-8");// import <任意标识符> from ["']@/index.html["']const importRegex = /import\s+(\w+)\s+from\s*["']@\/index\.html["']/g;// 检查是否匹配成功if (!importRegex.test(code)) {console.log(`[${PROJECT_NAME}] No matching HTML import found. Skipping fix.`);process.exit(0);}importRegex.lastIndex = 0;const fixedCode = code.replace(importRegex, (match, identifier) => {console.log(`[${PROJECT_NAME}] Restoring import: ${match}`);return `import ${identifier} from "../index.html"`;});writeFileSync(distFile, fixedCode);console.log(`[${PROJECT_NAME}] HTML imports restored successfully!`);
}main();

最后整合一下整个过程,沉淀到package.json的 scripts 里:

{"scripts": {"dev": "bun --hot src/backend/beIndex.tsx","start": "NODE_ENV=production bun src/backend/beIndex.tsx","build:all": "bun build:fe && bun build:be","build:fe": "bun run build.ts","build:be": "bun build:be1 && bun build:be2","build:be1": "bun build src/backend/beIndex.tsx --outdir dist/backend --target bun --external '@/index.html' --production","build:be2": "bun run scripts/fixHtmlImport.ts","run:dist": "bun run dist/backend/beIndex.js"}
}

执行bun build:all即可完成打包。执行bun run:dist即可启动后端项目。前端项目和后端接口跑在同一个端口 5202 下。

为所有页面添加统一的导航栏和页脚

给 Cursor 的 Prompt:

请为每个页面添加统一的导航栏和页脚,组件命名为 Layout.tsx。

导航栏:要求页面滚动时一直贴着视窗顶部,但在顶部时不要遮挡页面其他部分的内容。导航栏内的左侧有一个“Markdown 文章展示”加粗,是指向首页的链接。右侧有一个 GitHub 图标,也是链接,地址为 https://github.com/Hans774882968/bun-markdown-display

页脚:Copyright.tsx。分两行,第一行是 Made with ❤ in {currentYear} by,第二行是一个链接,文本为 Hans,链接为 https://github.com/Hans774882968

出来效果很不错,微调一下:

  1. nav-brand 离左侧 80px、GitHub 图标离右侧 80px。
  2. 页脚的“Hans”左侧加一个 GitHub 图标,并加粗。
  3. 导航栏和页脚的背景颜色应和 main-content 的黑色接近但又略有差别,并添加阴影,使得两者和 main-content 之间有视觉上的差别。记得对应修改文字颜色为某种白色。
  4. 导航栏的“Markdown 文章展示”左边加一个 Markdown 的图标。导航栏左侧添加面包屑。首页保持原样,文字页面的显示类似于“Markdown 文章展示 > 文章”。

效果:

在这里插入图片描述

体验感受

bun 热更新时会直接死掉:

panic(main thread): Segmentation fault at address 0x31CAE950140
oh no: Bun has crashed. This indicates a bug in Bun, not your code.To send a redacted crash report to Bun's team,
please file a GitHub issue using the link below:

盲猜是我电脑的磁盘空间不够导致的,暂时不知道原因。

参考资料

  1. 看看别人怎么做静态资源解析的:https://github.com/danawoodman/bun-htmx/blob/main/src/response.tsx
  2. https://juejin.cn/post/7273743139977183232

相关文章:

  • OpenHarmony外设驱动使用 (十五),USB
  • 【C/C++】从零开始掌握Kafka
  • Linux 下VS Code 的使用
  • 从AD9361 到 ADSY1100 ,中间的迭代产品历史
  • 动态规划-蓝桥杯-健身
  • 大数据学习(118)-SQL面试问题总结
  • RAG系统构建之嵌入模型性能优化完整指南
  • QML学习07Property
  • vue+js 创造动态的光晕圈
  • 8:OpenCV—仿射变换和坐标映射
  • Linux的进程控制
  • 从工程实践角度分析H.264与H.265的技术差异
  • 如何将ChatGPT添加到WordPress(新手指南)
  • 大数据与数据分析【数据分析全栈攻略:爬虫+处理+可视化+报告】
  • 华为OD机试真题—— 矩阵中非1的数量 (2025B卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • 顺丰社招入职SHL测评真题讲解| 3天备考、综合能力及性格问卷答题讲解
  • 打羽毛球tips
  • Bochs下去运行linux-0.11
  • 定时任务调度平台XXL-JOB
  • JavaSE核心知识点03高级特性03-05(注解)
  • 山东华邦建设网站首页/小红书关键词排名
  • 温州哪里有网站建设/宁波seo网站排名优化公司
  • 网站建设美工招聘/商丘优化公司
  • 专业网站建设哪家权威/外贸推广
  • 嘉兴云推广网站/google广告投放技巧
  • 做趣味图形的网站/活动营销案例100例