<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>猫猫的blog</title><description>记录生活与代码的日常</description><link>https://fuwari.vercel.app/</link><language>zh_CN</language><item><title>博客数据库迁移：从 SQLite 到 PostgreSQL + 1Panel 部署指南</title><link>https://fuwari.vercel.app/posts/database-migration-1panel-deploy/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/database-migration-1panel-deploy/</guid><description>记录博客后台管理系统的数据库架构升级，实现 SQLite 和 PostgreSQL 双数据库支持，以及在 1Panel 上的完整部署流程。</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;对于个人博客来说，初期开发阶段用 SQLite 是最省心的选择——零配置、单文件、随拿随走。但当博客要正式上线、长期运行时，SQLite 的局限性就显现了：并发写入能力弱、不方便做远程备份、无法多实例共享。&lt;/p&gt;
&lt;p&gt;我的博客项目（基于 Astro + Svelte）有一套后台管理系统，用于统计访问量、管理文章和 AI 摘要等功能。本地开发时一直用 SQLite，体验很好。但部署到 VPS 后，我需要一个更可靠的数据库方案。&lt;/p&gt;
&lt;p&gt;于是我做了一个决定：&lt;strong&gt;不是换掉 SQLite，而是同时支持 SQLite 和 PostgreSQL&lt;/strong&gt;，通过环境变量一键切换。开发时继续用 SQLite，生产环境切到 PostgreSQL。&lt;/p&gt;
&lt;p&gt;这篇文章就记录整个过程的思路和部署步骤。&lt;/p&gt;
&lt;h2&gt;架构设计：数据库驱动层&lt;/h2&gt;
&lt;h3&gt;接口抽象&lt;/h3&gt;
&lt;p&gt;核心思路很简单——定义一个数据库驱动接口 &lt;code&gt;DbDriver&lt;/code&gt;，所有数据库操作都通过这个接口完成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export interface DbDriver {
  init(): Promise&amp;lt;void&amp;gt;;
  recordVisit(pagePath: string): Promise&amp;lt;void&amp;gt;;
  recordArticleView(slug: string): Promise&amp;lt;void&amp;gt;;
  getStats(range: &quot;7d&quot; | &quot;30d&quot; | &quot;all&quot;): Promise&amp;lt;{
    totalPageViews: number;
    totalArticleViews: number;
    dailyViews: { date: string; count: number }[];
    topArticles: { slug: string; count: number }[];
  }&amp;gt;;
  createAdmin(username: string, password: string): Promise&amp;lt;boolean&amp;gt;;
  verifyAdmin(username: string, password: string): Promise&amp;lt;boolean&amp;gt;;
  listAdmins(): Promise&amp;lt;{ id: number; username: string; createdAt: string }[]&amp;gt;;
  changePassword(username: string, newPassword: string): Promise&amp;lt;boolean&amp;gt;;
  deleteAdmin(username: string): Promise&amp;lt;boolean&amp;gt;;
  close(): Promise&amp;lt;void&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;两套驱动实现&lt;/h3&gt;
&lt;p&gt;基于这个接口，我分别实现了两个驱动：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQLite 驱动&lt;/strong&gt;（&lt;code&gt;db-sqlite.ts&lt;/code&gt;）—— 使用 &lt;code&gt;better-sqlite3&lt;/code&gt;，数据存储在 &lt;code&gt;data/stats.db&lt;/code&gt; 文件中&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL 驱动&lt;/strong&gt;（&lt;code&gt;db-postgres.ts&lt;/code&gt;）—— 使用 &lt;code&gt;pg&lt;/code&gt; 库，通过连接字符串连接远程数据库&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;环境变量切换&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;DB_TYPE&lt;/code&gt; 环境变量决定加载哪个驱动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# DB_TYPE=sqlite（默认） → 使用 SQLite
# DB_TYPE=postgres       → 使用 PostgreSQL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样上层代码完全不关心底层用的是哪个数据库，切换只需改一个环境变量。&lt;/p&gt;
&lt;h2&gt;本地开发：SQLite 模式&lt;/h2&gt;
&lt;p&gt;SQLite 模式是默认模式，&lt;strong&gt;零配置即可使用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;项目根目录下的 &lt;code&gt;.env&lt;/code&gt; 文件中确保以下配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DB_TYPE=sqlite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;就这么简单。启动项目后，数据库文件会自动创建在 &lt;code&gt;data/stats.db&lt;/code&gt;。首次启动时会自动建表并创建默认管理员账号（&lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;admin12345678&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;本地开发完全不需要关心数据库安装和配置的问题，这是 SQLite 模式最大的优势。&lt;/p&gt;
&lt;h2&gt;迁移到 PostgreSQL&lt;/h2&gt;
&lt;h3&gt;安装 PostgreSQL&lt;/h3&gt;
&lt;p&gt;如果你使用 1Panel 部署，可以直接在应用商店中搜索并安装 PostgreSQL，非常方便。其他方式（如系统包管理器安装）也可以，这里不展开。&lt;/p&gt;
&lt;h3&gt;创建数据库和用户&lt;/h3&gt;
&lt;p&gt;安装好 PostgreSQL 后，需要创建专用的数据库和用户。通过 &lt;code&gt;psql&lt;/code&gt; 或 1Panel 的数据库管理界面操作：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE USER fuwari_user WITH PASSWORD &apos;your_secure_password&apos;;
CREATE DATABASE fuwari_blog OWNER fuwari_user;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;修改环境变量&lt;/h3&gt;
&lt;p&gt;在项目根目录创建或编辑 &lt;code&gt;.env&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DB_TYPE=postgres
DATABASE_URL=postgresql://fuwari_user:your_password@localhost:5432/fuwari_blog
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;密码中特殊字符的 URL 编码&lt;/h3&gt;
&lt;p&gt;这里有一个容易踩的坑：&lt;code&gt;DATABASE_URL&lt;/code&gt; 是一个连接字符串，如果密码中包含特殊字符，&lt;strong&gt;必须进行 URL 编码&lt;/strong&gt;，否则连接会失败。&lt;/p&gt;
&lt;p&gt;常见的需要编码的字符：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字符&lt;/th&gt;
&lt;th&gt;编码&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%23&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%40&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%2A&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;!&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%21&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%24&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%25&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;举个例子，如果你的密码是 &lt;code&gt;my#pass@word&lt;/code&gt;，那么连接字符串应该写成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=postgresql://fuwari_user:my%23pass%40word@localhost:5432/fuwari_blog
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1Panel 部署完整步骤&lt;/h2&gt;
&lt;p&gt;下面是在 1Panel 上从零部署的完整流程。&lt;/p&gt;
&lt;h3&gt;前置条件&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;一台 Linux VPS（推荐 Ubuntu 22.04+）&lt;/li&gt;
&lt;li&gt;已安装 1Panel（推荐从官网安装）&lt;/li&gt;
&lt;li&gt;Node.js 18+（可通过 1Panel 应用商店安装）&lt;/li&gt;
&lt;li&gt;PM2 进程管理器&lt;/li&gt;
&lt;li&gt;Nginx（1Panel 自带或应用商店安装）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;第一步：安装 PostgreSQL&lt;/h3&gt;
&lt;p&gt;在 1Panel 面板中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;进入 &lt;strong&gt;应用商店&lt;/strong&gt; → 搜索 &lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;选择合适的版本安装&lt;/li&gt;
&lt;li&gt;安装完成后记住默认端口（通常是 &lt;code&gt;5432&lt;/code&gt;）和管理员密码&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;第二步：创建数据库&lt;/h3&gt;
&lt;p&gt;通过 1Panel 的数据库管理界面，或者 SSH 进入服务器用 &lt;code&gt;psql&lt;/code&gt; 命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE USER fuwari_user WITH PASSWORD &apos;your_secure_password&apos;;
CREATE DATABASE fuwari_blog OWNER fuwari_user;
GRANT ALL PRIVILEGES ON DATABASE fuwari_blog TO fuwari_user;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第三步：克隆项目并构建&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 克隆项目
git clone https://github.com/your-username/fuwari.git
cd fuwari

# 安装 pnpm（如果没有）
npm install -g pnpm

# 安装依赖
pnpm install

# 构建项目
pnpm build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第四步：配置环境变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cp .env.example .env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑 &lt;code&gt;.env&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 数据库配置
DB_TYPE=postgres
DATABASE_URL=postgresql://fuwari_user:your_secure_password@localhost:5432/fuwari_blog

# AI 摘要功能（可选）
OPENROUTER_API_KEY=your-key-here
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第五步：配置 PM2&lt;/h3&gt;
&lt;p&gt;项目根目录已有 &lt;code&gt;ecosystem.config.js&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  apps: [{
    name: &apos;fuwari-blog&apos;,
    script: &apos;dist/server/entry.mjs&apos;,
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: &apos;512M&apos;,
    env: {
      NODE_ENV: &apos;production&apos;,
      PORT: 4321,
      HOST: &apos;0.0.0.0&apos;,
      DB_TYPE: &apos;postgres&apos;,
      DATABASE_URL: &apos;postgresql://fuwari_user:your_secure_password@localhost:5432/fuwari_blog&apos;
    }
  }]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动项目：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pm2 start ecosystem.config.js
pm2 save
pm2 startup  # 设置开机自启
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第六步：配置 Nginx 反向代理&lt;/h3&gt;
&lt;p&gt;在 1Panel 中创建网站，配置 Nginx 反向代理将域名指向本地服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:4321;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;第七步：在 1Panel 中添加网站&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;进入 &lt;strong&gt;网站&lt;/strong&gt; → &lt;strong&gt;创建网站&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;填写域名（如 &lt;code&gt;your-domain.com&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;选择 &lt;strong&gt;反向代理&lt;/strong&gt; 模式&lt;/li&gt;
&lt;li&gt;代理地址填写 &lt;code&gt;http://127.0.0.1:4321&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如需 HTTPS，可在 1Panel 中申请 Let&apos;s Encrypt 证书&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;完成后访问你的域名，应该能看到博客首页了。首次启动时会自动初始化数据库表和默认管理员。&lt;/p&gt;
&lt;h2&gt;环境变量参考&lt;/h2&gt;
&lt;p&gt;完整的 &lt;code&gt;.env&lt;/code&gt; 配置示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 数据库类型：sqlite 或 postgres
DB_TYPE=postgres

# PostgreSQL 连接字符串
DATABASE_URL=postgresql://fuwari_user:your_secure_password@localhost:5432/fuwari_blog

# AI 摘要功能（可选）
OPENROUTER_API_KEY=your-key-here
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;切换回 SQLite&lt;/h2&gt;
&lt;p&gt;如果你想在本地切回 SQLite 模式，只需要修改一个环境变量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DB_TYPE=sqlite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不需要安装任何东西，不需要其他配置。重启项目后会自动使用 &lt;code&gt;data/stats.db&lt;/code&gt; 文件作为数据库。&lt;/p&gt;
&lt;p&gt;这种设计让你可以在开发和部署之间自由切换，非常灵活。&lt;/p&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;h3&gt;连接字符串中的特殊字符&lt;/h3&gt;
&lt;p&gt;再强调一次：PostgreSQL 连接字符串中，密码部分如果包含 &lt;code&gt;#&lt;/code&gt;、&lt;code&gt;@&lt;/code&gt;、&lt;code&gt;*&lt;/code&gt;、&lt;code&gt;!&lt;/code&gt;、&lt;code&gt;$&lt;/code&gt; 等特殊字符，&lt;strong&gt;必须做 URL 编码&lt;/strong&gt;。这是一个很容易忽略但又很难排查的问题——连接失败时第一反应应该检查这里。&lt;/p&gt;
&lt;h3&gt;默认管理员账号&lt;/h3&gt;
&lt;p&gt;首次启动时（无论 SQLite 还是 PostgreSQL 模式），系统会自动创建默认管理员：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户名：&lt;code&gt;admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;密码：&lt;code&gt;admin12345678&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;部署到生产环境后，请立即修改默认密码！&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;自动创建默认管理员&lt;/h4&gt;
&lt;p&gt;通过 &lt;code&gt;.env&lt;/code&gt; 中的 &lt;code&gt;CREATE_DEFAULT_ADMIN&lt;/code&gt; 配置项控制是否自动创建默认管理员账号：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 设置为 true 时，首次启动自动创建默认管理员（默认值：true）
CREATE_DEFAULT_ADMIN=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你不需要默认管理员（例如已经手动创建了其他管理员），可以将其设为 &lt;code&gt;false&lt;/code&gt;，这样启动时不会自动创建 &lt;code&gt;admin&lt;/code&gt; 账号。&lt;/p&gt;
&lt;h4&gt;忘记密码：邮箱重置&lt;/h4&gt;
&lt;p&gt;登录页面新增了「忘记密码」功能。管理员在登录页点击「忘记密码」后，系统会通过 Resend 邮件服务向管理员绑定的邮箱发送密码重置链接。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;管理员可在管理面板中设置自己的邮箱地址&lt;/li&gt;
&lt;li&gt;重置链接通过邮件发送，点击后即可设置新密码&lt;/li&gt;
&lt;li&gt;需要在 &lt;code&gt;.env&lt;/code&gt; 中配置 Resend API Key 和发件人信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;CLI 密码重置&lt;/h4&gt;
&lt;p&gt;如果邮件服务不可用，也可以通过命令行脚本直接重置密码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node scripts/reset-admin-password.mjs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该脚本同时支持 SQLite 和 PostgreSQL 两种数据库模式，会根据 &lt;code&gt;DB_TYPE&lt;/code&gt; 环境变量自动选择对应的数据库驱动。&lt;/p&gt;
&lt;h3&gt;数据迁移&lt;/h3&gt;
&lt;p&gt;目前 SQLite 和 PostgreSQL 之间&lt;strong&gt;没有自动迁移工具&lt;/strong&gt;。如果你需要把本地 SQLite 的数据迁移到 PostgreSQL，需要手动操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导出 SQLite 数据（可以用 &lt;code&gt;sqlite3&lt;/code&gt; 命令行工具导出为 SQL 或 CSV）&lt;/li&gt;
&lt;li&gt;导入到 PostgreSQL（根据导出的格式用对应方式导入）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;后续版本可能会考虑加入自动迁移功能。&lt;/p&gt;
&lt;h3&gt;端口与安全&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;PostgreSQL 默认监听 &lt;code&gt;5432&lt;/code&gt; 端口，生产环境建议&lt;strong&gt;不要将数据库端口暴露到公网&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;确保 VPS 防火墙只开放必要的端口（80、443）&lt;/li&gt;
&lt;li&gt;数据库连接使用 &lt;code&gt;localhost&lt;/code&gt; 即可，不需要走外网&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;通过抽象数据库驱动接口，这个项目实现了 SQLite 和 PostgreSQL 的无缝切换：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开发阶段&lt;/strong&gt;：&lt;code&gt;DB_TYPE=sqlite&lt;/code&gt;，零配置，&lt;code&gt;data/stats.db&lt;/code&gt; 一个文件搞定&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产部署&lt;/strong&gt;：&lt;code&gt;DB_TYPE=postgres&lt;/code&gt;，配合 1Panel 快速搭建 PostgreSQL + Node.js + Nginx 的完整环境&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;整个过程的核心改动集中在数据库驱动层，上层业务代码完全不需要修改。如果你也在用 Astro 搭建带后台功能的博客，这种双数据库支持的架构值得参考。&lt;/p&gt;
&lt;p&gt;部署方面，1Panel 确实简化了不少工作——从数据库安装到 Nginx 配置，基本都能在面板里完成。配合 PM2 做进程管理，稳定性和可维护性都有保障。&lt;/p&gt;
</content:encoded></item><item><title>用 Biome 统一你的前端工具链</title><link>https://fuwari.vercel.app/posts/biome-unified-toolchain/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/biome-unified-toolchain/</guid><description>介绍 Biome 这个新兴的前端工具，看它如何用一个工具替代 ESLint + Prettier 的组合。</description><pubDate>Fri, 12 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;痛点：工具链碎片化&lt;/h2&gt;
&lt;p&gt;前端项目的工具链通常是这样的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ESLint&lt;/strong&gt;：代码检查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prettier&lt;/strong&gt;：代码格式化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stylelint&lt;/strong&gt;：样式检查&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三个工具，三套配置，三种运行方式。更麻烦的是，ESLint 和 Prettier 之间经常产生规则冲突，你需要额外安装 &lt;code&gt;eslint-config-prettier&lt;/code&gt; 来协调。&lt;/p&gt;
&lt;p&gt;有没有一个工具能统一这些功能？&lt;strong&gt;Biome&lt;/strong&gt; 就是为此而生的。&lt;/p&gt;
&lt;h2&gt;Biome 是什么&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://biomejs.dev/&quot;&gt;Biome&lt;/a&gt; 是一个用 Rust 编写的高性能前端工具链，它同时提供了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linter&lt;/strong&gt;：代码质量检查&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formatter&lt;/strong&gt;：代码格式化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Import Sorter&lt;/strong&gt;：导入语句排序&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个工具，一套配置，一次安装。&lt;/p&gt;
&lt;h2&gt;性能对比&lt;/h2&gt;
&lt;p&gt;Biome 最直观的优势是&lt;strong&gt;快&lt;/strong&gt;。官方基准测试显示：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作&lt;/th&gt;
&lt;th&gt;ESLint + Prettier&lt;/th&gt;
&lt;th&gt;Biome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;格式化 1000 个文件&lt;/td&gt;
&lt;td&gt;~12s&lt;/td&gt;
&lt;td&gt;~0.3s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;检查 1000 个文件&lt;/td&gt;
&lt;td&gt;~8s&lt;/td&gt;
&lt;td&gt;~0.4s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;快了将近 &lt;strong&gt;30-40 倍&lt;/strong&gt;。在 CI/CD 中，这意味着更短的等待时间和更低的计算成本。&lt;/p&gt;
&lt;h2&gt;快速上手&lt;/h2&gt;
&lt;p&gt;安装非常简单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install --save-dev @biomejs/biome
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初始化配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx biome init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会生成一个 &lt;code&gt;biome.json&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;$schema&quot;: &quot;https://biomejs.dev/schemas/1.9.0/schema.json&quot;,
  &quot;organizeImports&quot;: {
    &quot;enabled&quot;: true
  },
  &quot;linter&quot;: {
    &quot;enabled&quot;: true,
    &quot;rules&quot;: {
      &quot;recommended&quot;: true
    }
  },
  &quot;formatter&quot;: {
    &quot;enabled&quot;: true,
    &quot;indentStyle&quot;: &quot;space&quot;,
    &quot;indentWidth&quot;: 2
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;package.json&lt;/code&gt; 中添加脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;lint&quot;: &quot;biome check src/&quot;,
    &quot;format&quot;: &quot;biome format --write src/&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;与现有工具对比&lt;/h2&gt;
&lt;h3&gt;vs ESLint&lt;/h3&gt;
&lt;p&gt;Biome 的 lint 规则覆盖了 ESLint 推荐规则的大部分。对于常规项目完全够用。如果你的项目依赖某些 ESLint 插件的特殊规则，可以通过 &lt;code&gt;@biomejs/eslint-plugin&lt;/code&gt; 桥接使用。&lt;/p&gt;
&lt;h3&gt;vs Prettier&lt;/h3&gt;
&lt;p&gt;Biome 的格式化输出与 Prettier 高度兼容。它支持相同的配置选项（缩进风格、引号、分号等），迁移成本极低。&lt;/p&gt;
&lt;h2&gt;编辑器集成&lt;/h2&gt;
&lt;p&gt;Biome 提供了 VS Code 和 JetBrains 系列 IDE 的官方插件。安装后，保存时自动格式化，体验与 Prettier 一致。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;如果你的项目不需要 ESLint 生态中那些复杂的插件，Biome 是一个非常好的替代方案。它更快、更简单、配置更少，而且还在快速迭代中。&lt;/p&gt;
&lt;p&gt;工具链的简化不仅仅是少装几个包的问题——它降低了维护成本，减少了团队成员之间的配置争论，让大家都把精力集中在真正重要的事情上：&lt;strong&gt;写好代码&lt;/strong&gt;。&lt;/p&gt;
</content:encoded></item><item><title>编程三年，我学到的不只是技术</title><link>https://fuwari.vercel.app/posts/three-years-of-coding/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/three-years-of-coding/</guid><description>回顾三年的编程经历，分享那些课本和教程里学不到的经验与感悟。</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;写在前面&lt;/h2&gt;
&lt;p&gt;从写下第一行 &lt;code&gt;console.log(&quot;Hello World&quot;)&lt;/code&gt; 到现在，编程已经陪伴我走过了三年。这三年里，我学到的远不止技术本身。&lt;/p&gt;
&lt;h2&gt;1. 先跑通，再优化&lt;/h2&gt;
&lt;p&gt;刚学编程时，我花了大量时间在&quot;最佳实践&quot;上。一个变量命名纠结半天，代码结构反复调整，结果项目迟迟没有进展。&lt;/p&gt;
&lt;p&gt;后来我意识到，&lt;strong&gt;完成比完美更重要&lt;/strong&gt;。先让功能跑通，有了可工作的原型，再去重构和优化。这并不意味着写烂代码，而是不要在早期过度追求完美。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;Make it work, make it right, make it fast.&quot; —— Kent Beck&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2. 读源码是最好的老师&lt;/h2&gt;
&lt;p&gt;遇到问题时，很多人的第一反应是搜索答案。但我发现，直接阅读相关库的源码往往能获得更深的理解。&lt;/p&gt;
&lt;p&gt;有一次我在调试一个 CSS 布局问题，查了半天文档都没搞明白。最后我去看了 Flexbox 的规范文档，反而一次性理解了整个布局模型的本质。&lt;/p&gt;
&lt;p&gt;源码不会骗你，它展示的是设计者真正的意图。&lt;/p&gt;
&lt;h2&gt;3. 写博客是最高效的学习方式&lt;/h2&gt;
&lt;p&gt;费曼学习法说得好：&lt;strong&gt;如果你不能简单地解释一件事，说明你还没有真正理解它&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;写技术博客强迫我整理思路，把模糊的理解变成清晰的文字。很多时候，写着写着就发现了自己知识体系中的漏洞。&lt;/p&gt;
&lt;p&gt;而且，博客还有一个额外的好处——它帮你建立个人品牌。我现在的几个工作机会，都跟博客有关。&lt;/p&gt;
&lt;h2&gt;4. 不要追逐所有新技术&lt;/h2&gt;
&lt;p&gt;前端社区几乎每周都有新工具、新框架出现。我曾经陷入过&quot;什么都想学&quot;的焦虑中，结果什么都没学精。&lt;/p&gt;
&lt;p&gt;现在我学会了做减法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;核心技能深入学&lt;/strong&gt;：JavaScript 基础、CSS 布局、网络协议、数据结构&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;框架按需学习&lt;/strong&gt;：用到什么学什么，不用的不碰&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;新工具观望为主&lt;/strong&gt;：等社区沉淀一段时间再决定是否跟进&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技术的底层原理是相通的，基础扎实了，学新东西会快很多。&lt;/p&gt;
&lt;h2&gt;5. 休息和编码一样重要&lt;/h2&gt;
&lt;p&gt;曾经我以为&quot;肝代码&quot;是进步的捷径，熬夜写项目是常事。直到有一次连续加班两周后，代码质量明显下降，bug 反而更多。&lt;/p&gt;
&lt;p&gt;现在我保持每天固定时间写代码，周末尽量不碰工作。 令人惊讶的是，效率反而比之前更高了。&lt;/p&gt;
&lt;p&gt;大脑需要休息来巩固记忆和整理思路。很多时候，最好的解决方案不是在电脑前想出来的，而是在散步、洗澡或者睡觉时突然冒出来的。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;编程教会我的不仅是如何写代码，更是如何思考、如何学习、如何平衡工作与生活。这些经验或许不够&quot;技术&quot;，但我相信它们对每个编程路上的同行者都有价值。&lt;/p&gt;
</content:encoded></item><item><title>为什么我选择 Astro 构建个人博客</title><link>https://fuwari.vercel.app/posts/why-astro/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/why-astro/</guid><description>在尝试了多种静态站点生成器之后，我最终选择了 Astro。这篇文章分享我的选型思路和 Astro 的核心优势。</description><pubDate>Tue, 20 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;去年我决定重新搭建个人博客，核心诉求很简单：&lt;strong&gt;加载快、写作体验好、部署方便&lt;/strong&gt;。在对比了 Hugo、Next.js、Hexo 等方案后，我最终选择了 Astro。&lt;/p&gt;
&lt;h2&gt;Astro 的核心优势&lt;/h2&gt;
&lt;h3&gt;1. 零 JavaScript 默认输出&lt;/h3&gt;
&lt;p&gt;Astro 最吸引我的特性是&quot;默认零 JS&quot;。它会将模板编译为纯 HTML，不会向浏览器发送不必要的 JavaScript 代码。这对于以内容为主的博客来说至关重要。&lt;/p&gt;
&lt;p&gt;根据官方数据，Astro 站点平均比同功能的 Next.js 站点快 &lt;strong&gt;40%&lt;/strong&gt; 以上。&lt;/p&gt;
&lt;h3&gt;2. 岛屿架构&lt;/h3&gt;
&lt;p&gt;Astro 独创的&quot;岛屿（Islands）&quot;架构允许你在静态页面中嵌入交互式组件，而且这些组件可以来自不同的框架：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
import ReactCounter from &apos;../components/ReactCounter.jsx&apos;;
import SvelteTimer from &apos;../components/SvelteTimer.svelte&apos;;
---

&amp;lt;!-- 两个独立的交互岛屿 --&amp;gt;
&amp;lt;ReactCounter client:load /&amp;gt;
&amp;lt;SvelteTimer client:visible /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着你可以在同一个页面中混用 React、Vue、Svelte 组件，按需加载，互不干扰。&lt;/p&gt;
&lt;h3&gt;3. 基于内容集合的类型安全&lt;/h3&gt;
&lt;p&gt;Astro 的内容集合（Content Collections）基于 Zod 做 schema 校验，在构建时就能发现 frontmatter 中的错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// content/config.ts
import { defineCollection, z } from &quot;astro:content&quot;;

const posts = defineCollection({
  schema: z.object({
    title: z.string(),
    published: z.date(),
    draft: z.boolean().optional(),
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果你漏写了 &lt;code&gt;title&lt;/code&gt; 字段，构建时就会报错，而不是等到线上才发现问题。&lt;/p&gt;
&lt;h3&gt;4. 强大的 Markdown 支持&lt;/h3&gt;
&lt;p&gt;Astro 内置了对 Markdown 的全面支持，包括 frontmatter 解析、自动目录生成、以及通过 rehype/remark 插件扩展的能力。你可以轻松添加代码高亮、数学公式、自定义容器等功能。&lt;/p&gt;
&lt;h3&gt;5. 部署灵活&lt;/h3&gt;
&lt;p&gt;Astro 的输出是标准的静态文件，可以部署到任何支持静态托管的平台。我目前部署在 Vercel 上，每次 &lt;code&gt;git push&lt;/code&gt; 后自动构建，全程零配置。&lt;/p&gt;
&lt;h2&gt;一些不足&lt;/h2&gt;
&lt;p&gt;当然，Astro 也不是完美的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;生态相对年轻&lt;/strong&gt;：相比 Next.js，Astro 的插件和社区资源还比较少&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习成本&lt;/strong&gt;：模板语法需要一定时间适应，尤其是从 React/Vue 转过来的开发者&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSR 支持有限&lt;/strong&gt;：虽然 Astro 4.0 开始支持 SSR，但这并非它的强项&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;如果你的需求是构建一个&lt;strong&gt;以内容为核心&lt;/strong&gt;的网站——博客、文档站、作品集——Astro 几乎是目前最优的选择。它在性能和开发体验之间找到了一个很好的平衡点。&lt;/p&gt;
</content:encoded></item><item><title>五个提升效率的前端开发技巧</title><link>https://fuwari.vercel.app/posts/five-frontend-tips/</link><guid isPermaLink="true">https://fuwari.vercel.app/posts/five-frontend-tips/</guid><description>分享五个在日常前端开发中非常实用的技巧，帮助你写出更简洁、更高效的代码。</description><pubDate>Sat, 15 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;前端开发日新月异，掌握一些实用技巧可以大幅提升开发效率和代码质量。今天分享五个我在日常工作中经常用到的技巧。&lt;/p&gt;
&lt;h2&gt;1. 使用可选链操作符&lt;/h2&gt;
&lt;p&gt;在处理嵌套对象时，可选链操作符 &lt;code&gt;?.&lt;/code&gt; 可以避免大量的空值判断：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 传统写法
const city = user &amp;amp;&amp;amp; user.address &amp;amp;&amp;amp; user.address.city;

// 使用可选链
const city = user?.address?.city;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码瞬间简洁了很多，而且不会因为中间值为 &lt;code&gt;undefined&lt;/code&gt; 而报错。&lt;/p&gt;
&lt;h2&gt;2. 利用 &lt;code&gt;structuredClone&lt;/code&gt; 深拷贝对象&lt;/h2&gt;
&lt;p&gt;以前深拷贝通常用 &lt;code&gt;JSON.parse(JSON.stringify(obj))&lt;/code&gt;，但它无法处理 &lt;code&gt;Date&lt;/code&gt;、&lt;code&gt;RegExp&lt;/code&gt; 等特殊类型。现代浏览器已原生支持 &lt;code&gt;structuredClone&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const original = { name: &quot;Alice&quot;, birthday: new Date(&quot;2000-01-01&quot;) };
const cloned = structuredClone(original);

console.log(cloned.birthday instanceof Date); // true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. CSS 容器查询&lt;/h2&gt;
&lt;p&gt;媒体查询是基于视口宽度的，但组件往往需要基于自身容器的大小来调整样式。CSS 容器查询解决了这个问题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.card-container {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 2fr;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这让组件真正做到了&quot;自适应&quot;，无论放在页面的哪个位置都能正确响应。&lt;/p&gt;
&lt;h2&gt;4. 用 &lt;code&gt;AbortController&lt;/code&gt; 取消请求&lt;/h2&gt;
&lt;p&gt;当组件卸载或用户切换页面时，应该取消未完成的网络请求，避免内存泄漏和不必要的资源消耗：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const controller = new AbortController();

fetch(&quot;/api/data&quot;, { signal: controller.signal })
  .then(res =&amp;gt; res.json())
  .then(data =&amp;gt; console.log(data))
  .catch(err =&amp;gt; {
    if (err.name === &quot;AbortError&quot;) {
      console.log(&quot;请求已取消&quot;);
    }
  });

// 需要取消时
controller.abort();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 善用 &lt;code&gt;console.table&lt;/code&gt; 调试&lt;/h2&gt;
&lt;p&gt;当需要查看一组结构相似的数据时，&lt;code&gt;console.table&lt;/code&gt; 比 &lt;code&gt;console.log&lt;/code&gt; 直观得多：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const users = [
  { name: &quot;张三&quot;, age: 28, role: &quot;前端&quot; },
  { name: &quot;李四&quot;, age: 32, role: &quot;后端&quot; },
];

console.table(users);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器控制台会以表格形式展示数据，一目了然。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;这五个技巧都是我在实际项目中反复验证过的。好的代码不仅仅是&quot;能运行&quot;，更要追求简洁、可维护和高性能。希望这些技巧对你也有所帮助。&lt;/p&gt;
</content:encoded></item></channel></rss>