普通视图

发现新文章,点击刷新页面。
昨天以前程沛权 - 养了三只猫

适用于 Oxlint 与 Oxfmt 的 Oxc 优先工作流

很早之前就对 Oxc 这个项目一直保持关注,看到前段时间开始进入 1.x 版本,一直想弄一套配置,这几天终于有时间搞一下。

不过目前阶段 OXLint 还是无法完整替代 ESLint ,只能是以 OXLint 为主, ESLint 为辅,但是两者可以完美结合,对项目来说,仍然可以以一套 Lint 方案去解决代码问题, OXFmt 倒是可以完美代替 Prettier 原生规则,只是部分插件仍然需要取舍,例如 Markdown 的盘古之白格式化,我只能暂时舍弃。

由于需要两套 Lint 同时工作,所以这个配置包还没有正式定名为 oxc-config ,暂时只称之为 Integration ,一个集成方案或者说是一套工作流。

这是我在 GitHub 上发布的一个开源项目。如果它对你有帮助,欢迎前往 bassist 点一个 Star。

@bassist/oxc-integration 是一个 Oxc 优先的工作流包,适合希望现在就用上带类型提示的 oxlint.config.tsoxfmt.config.ts,同时在 Oxc 能力尚未完全覆盖时保留 ESLint 兜底能力的项目。

使用方法

通常只需要四步:

  1. 安装依赖(参考:安装
  2. 添加 oxlint.config.ts(参考:Oxlint 快速开始
  3. 添加 oxfmt.config.ts(参考:Oxfmt 快速开始
  4. 只有在项目需要 fallback coverage 时,再补 eslint.config.js(参考:ESLint Fallback 快速开始

安装

npm i -D oxlint oxfmt @bassist/oxc-integration

如果你的项目需要 ESLint fallback,请再安装对应框架所需的 ESLint 生态依赖。

编辑器设置

oxlint / eslint 的 CLI 检查结果,与编辑器里的实时诊断并不是同一件事。

如果你希望在编码过程中就获得 Oxc 的实时 lint 提示,还需要额外安装对应的编辑器插件。否则就可能出现“执行 lint 命令时能查出错误,但编辑器里没有任何 提示”的情况。

  • VS Code / Cursor:安装官方 oxc.oxc-vscode 扩展
  • 其他编辑器:参考 Oxc 官方的编辑器接入文档

官方文档: https://oxc.rs/docs/guide/usage/linter/editors.html

Oxc 的编辑器扩展会通过项目本地的 oxlint --lsp 工作,因此也请确保项目的 devDependencies 里已经安装了 oxlint

如果你使用 VS Code 或 Cursor,也可以在项目里补一个 .vscode/settings.json,让保存时修复和类型感知检查更完整:

{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "always",
    "source.fixAll.oxc": "always"
  },
  "oxc.typeAware": true
}
  • source.fixAll.oxc 用于通过 oxc.oxc-vscode 扩展启用 Oxc 的保存时自动修复
  • oxc.typeAware: true 可以在 TypeScript 项目里提供类型感知诊断
  • 如果你的项目还保留 eslint.config.js 作为 fallback layer,建议继续保留 source.fixAll.eslint
  • 如果项目已经是纯 Oxlint 工作流,可以移除 ESLint 的保存时动作

定位

  • 如果你需要当前稳定、完整的 ESLint 优先方案,请使用 @bassist/eslint-config
  • 如果你想采用 Oxc 优先的工作流,请使用 @bassist/oxc-integration
  • 可以把它理解为未来 @bassist/oxc-config 的过渡形态

工作方式

@bassist/oxc-integration 采用 Oxc-first workflow:

  • oxlint 是主 Linter
  • eslint 是兜底 Linter,用来补齐 Oxc 还没完全覆盖的规则
  • oxfmt 是主 Formatter

这也是为什么某些项目现在仍然会同时保留 oxlint.config.tseslint.config.js。 它们不再是两个并列的主配置文件,而是:

  • oxlint.config.ts:主 Lint 配置
  • eslint.config.js:兜底 Lint 配置

在这些 fallback presets 内部,会自动接入 eslint-plugin-oxlint,用于关闭那些已经由 Oxlint 覆盖的 ESLint 重叠规则。

什么时候需要 ESLint Fallback

如果 Oxc 已经足够覆盖你的项目,可以只使用 oxlint.config.ts。 如果项目仍然依赖框架或生态特定的 ESLint 能力,则再补一个 eslint.config.js

通常可以这样理解:

  • base / node:很多场景只用 Oxlint 就够了
  • react:大多数情况下可以 Oxc 优先,但很多项目仍建议保留 ESLint fallback
  • vue:默认建议保留 ESLint fallback
  • next:默认建议保留 ESLint fallback
  • tailwindcss:如果项目使用 Tailwind CSS 原子类,建议补上 ESLint fallback
  • vitest:如果你需要更完整的测试规则,建议保留 ESLint fallback

内建的 Fallback Coverage

当前 @bassist/oxc-integration 已内建这些 ESLint fallback presets:

  • javascript
  • typescript
  • jsx
  • imports
  • markdown
  • react
  • tailwindcss
  • vue
  • next
  • vitest

当前暂不纳入这个 Oxc-first 工作流范围的内容:

  • lint-md
  • 基于 Prettier 的 Markdown 内容工作流

这些能力后续可以单独作为内容层工作流再设计。

配置总览

项目类型 oxlint.config.ts eslint.config.js oxfmt.config.ts
Base TS/JS 必需 可选 必需
Node 必需 可选 必需
React 必需 推荐 必需
Vue 必需 推荐 必需
Next 必需 推荐 必需

Oxlint 快速开始

Base TS/JS

// oxlint.config.ts
import { defineOxlintConfig, oxlintPresets } from '@bassist/oxc-integration'

export default defineOxlintConfig(oxlintPresets.base())

React

// oxlint.config.ts
import { defineOxlintConfig, oxlintPresets } from '@bassist/oxc-integration'

export default defineOxlintConfig(oxlintPresets.react(), oxlintPresets.vitest())

Vue

// oxlint.config.ts
import { defineOxlintConfig, oxlintPresets } from '@bassist/oxc-integration'

export default defineOxlintConfig(oxlintPresets.vue(), oxlintPresets.vitest())

如果你需要官方 oxlint 类型,请直接从 oxlint 包导入,而不是从本包导入。

Oxlint 内建默认值

oxlintPresets.base() 并不是空壳,它已经内置了这个包约定的 Oxc 基线配置。

当前内建默认值包括:

  • plugins:typescriptoxc
  • categories:
    • correctness: error
    • suspicious: error
    • pedantic: warn
    • style: off
  • 基础规则覆盖:
    • typescript/no-explicit-any: off

然后更高层的 presets 会在这份基线上继续扩展:

  • oxlintPresets.node() 会补充 Node 运行时环境
  • oxlintPresets.react() 会补充 reactreact-perfjsx-a11y
  • oxlintPresets.vue() 会补充 vue
  • oxlintPresets.vitest() 会补充 vitest

如何扩展 Oxlint Presets

建议先使用内建默认值,再把项目特有的差异作为额外配置传入。

// oxlint.config.ts
import { defineOxlintConfig, oxlintPresets } from '@bassist/oxc-integration'

export default defineOxlintConfig(oxlintPresets.react(), {
  rules: {
    'no-console': 'warn',
  },
  ignorePatterns: ['fixtures/**'],
})

可以把 presets 理解成默认基线,把额外传入的对象理解成项目级扩展层。

ESLint Fallback 快速开始

Vue / Next / 复杂 React 项目

// eslint.config.js
import { defineEslintConfig, eslintPresets } from '@bassist/oxc-integration'

export default defineEslintConfig(
  eslintPresets.imports(),
  eslintPresets.react(),
  eslintPresets.tailwindcss(),
  eslintPresets.vitest(),
)

如果是 Vue 项目,把 react() 换成 vue() 即可。 如果是 Next 项目,请使用 next()。 只有项目真的在使用 Tailwind CSS 时,才需要再加上 tailwindcss()

Markdown 内容 lint

// eslint.config.js
import { defineEslintConfig, eslintPresets } from '@bassist/oxc-integration'

export default defineEslintConfig(eslintPresets.markdown())

如果你只想为 Markdown 文件开启独立的 lint 行为,而不希望把它并入 lint-md 或基于 Prettier 的内容工作流,可以使用 markdown()

Oxfmt 快速开始

建议优先使用带类型提示的 oxfmt.config.ts,这样配置结构会与官方 formatter API 保持一致。

// oxfmt.config.ts
import { defineConfig } from 'oxfmt'
import { oxfmtConfig } from '@bassist/oxc-integration'

export default defineConfig(oxfmtConfig)

如果你想覆盖默认值:

// oxfmt.config.ts
import { defineConfig } from 'oxfmt'
import { getOxfmtConfig } from '@bassist/oxc-integration'

export default defineConfig(
  getOxfmtConfig({
    semi: true,
  }),
)

oxfmt . 会自动发现 oxfmt.config.ts,所以通常不需要额外显式传入配置路径。

如果你需要官方 formatter 类型,请直接从 oxfmt 包导入,而不是从本包导入。

推荐脚本

{
  "scripts": {
    "lint": "oxlint .",
    "lint:eslint": "eslint .",
    "lint:full": "npm run lint && npm run lint:eslint",
    "format": "oxfmt ."
  }
}

推荐用法:

  • base / node 项目先从 lint 开始
  • vuenext、以及较复杂的 react 项目默认建议使用 lint:full

Vue Picture Cropper 发布 1.x 版本 讲一讲背后的设计理念

这个春节假期对 vue-picture-cropper 这个包进行了一次改版,主要想解决一些工程设计上的老问题,例如 #49#45 这些 issue 提到的问题。

虽然是个 Breaking Change ,但对用户来说迁移成本不大,并且有了一些更灵活的用法(例如组合式函数),我自己则在重写源码时积累了一些思考点,在这篇文章里分享一下。

本文说明 vue-picture-cropper 从 v0.x 升级到 v1.x 时,在打包方式、样式加载和实例管理上的设计取舍与原因。

为什么会有这个包

在谈 1.x 的设计之前,有必要先回顾一下 0.x 的初衷。

0.x 的第一个版本发布于 2020 年 11 月。当时 Vue 3.x 刚刚发布,整体生态尚处于早期阶段,许多常用库还未完成适配,工程实践也尚未完全稳定。这导致当时的 Vue 3 业务项目很容易因为生态不足而延误工期。

因此,这个包最初并不是面向通用场景的组件库,而是一个为业务项目快速适配的个人小工具。那个时候我的设计目标非常明确:

  • 快速为 Vue 3 提供一层可用的 Cropper 封装
  • 尽可能降低接入成本
  • 以 “开箱即用” 为优先原则

因此在 0.x 中选择将 cropperjs 内置打包,用户只需安装一个包即可使用。

这个处理方式从当时的设计目标来看,是合理的取舍 —— 它优先解决了 “可用性” 和 “便捷性” 的问题,而不是工程边界与依赖模型的完备性。

但这几年随着项目逐步被更多场景使用,工程规模扩大、依赖关系复杂化,这种早期的便捷型设计也逐渐暴露出局限性。

1.x 的调整,并不是对 0.x 的否定,而是在使用场景变化之后的一次架构升级。

为什么仍然是 Cropper.js 1.x

虽然 Cropper.js 主分支已经切换到 2.x,但 v1 与 v2 在架构和使用方式上存在显著差异:

对比维度 1.x 特点 2.x 特点
架构 传统单体 JavaScript 库,所有 API 通过构造函数和配置项提供 基于 Web Components(自定义元素)重构,将不同能力拆分成可组合的元素(如 <cropper-image><cropper-selection>),表达方式更偏 “原生 DOM 组件化”
API 与使用模式 以配置项/方法为主,适合 Vue 或纯 JS 组件封装 一部分 API 通过 DOM 事件、属性和自定义元素组合替代原配置项,如 viewMode、dragMode 等迁移到不同元素的属性/事件
生态兼容性和迁移成本 API 形态对 Vue 封装友好,稳定且迁移成本低 Web Components 架构现代,但与 Vue 响应式和生命周期体系存在适配成本,需要额外桥接层
成熟度与稳定性 已长期稳定维护,用户基础大,语义明确 引入现代架构思路,但升级仍需用户适配 API 和行为,版本替换不够直接

基于以上差异,选择 v1 的工程考量如下:

  • 稳定可预测的 API,方便组件封装与调用
  • 低迁移成本与低学习成本,保持本库用户现有使用习惯
  • 生态友好,遇到问题更容易查找和解决
  • 面向现状工程使用,确保构建、SSR 和多实例场景可控

因此,本库依然选择依赖 Cropper.js 1.x ,而非 2.x ,以保证成熟稳定、可维护和生态兼容性。

如果希望使用 Cropper.js 2.x 的现代 Web Components 架构,建议直接使用 Cropper.js 原生库,而非本库的 Vue 封装层。

为什么仅提供 ESM 版本

从 1.x 开始,本库仅以 ESM(ES Modules)形式发布,不再提供 CommonJS (CJS) 或 IIFE 构建版本。

背景与原因:

  • 现代 Vue 项目默认使用 ESM

    绝大多数 Vue 3 项目都基于 Vite 或其他现代打包工具,这些环境原生支持 ESM。提供 CJS 或 IIFE 构建在这些场景下意义不大,同时增加维护成本。

  • IIFE / CDN 使用量极低

    通过 CDN 分发 IIFE 构建的场景在实践中非常少见,本库统计和社区反馈均显示几乎无人使用。继续提供会增加打包体积和测试负担,但对用户价值有限。

  • 简化构建与维护

    移除 CJS/IIFE 后,库的构建流程更简单,TypeScript 类型和模块导出更一致,也避免了 CJS 下 default + named export 的潜在混乱问题。

如果你的项目依赖 CJS / IIFE,请迁移到支持 ESM 的环境,例如使用 Vite、Nuxt 或现代 Webpack 版本。

关于 Bundle 的调整

在 0.x 中,Cropper.js 作为内部依赖被打包进 vue-picture-cropper 的产物中,用户只需安装一个包即可使用:

# 0.x 时的安装方式
npm i vue-picture-cropper

而在 1.x ,Cropper.js 不再被打包进本库的 Bundle ,需要由使用方在项目中显式安装并锁定 Cropper.js 1.x:

# 1.x 需要这么安装
npm i vue-picture-cropper cropperjs@^1

虽然 0.x 看起来方便,但在工程项目里存在这样的问题:

cropperjs 被打包进 vue-picture-cropper ,如果用户项目中也单独使用了 cropperjs ,或其他库也依赖 cropperjs ,就可能出现:多份 cropperjs 、多个实例冲突、工程产物体积变大

简单来说,0.x 是 “内置运行时依赖的封装组件” ,而 1.x 是 “对等依赖(peer dependency)模式下的 Vue 适配层” 。

这样做带来的好处,体现在在运行时层面是:

  • 避免重复打包与多实例问题(由宿主项目统一管理版本,只会存在一份依赖)
  • 保证运行时构造函数来源唯一
  • 降低原型链与 instanceof 判断异常的风险

体现在工程层面是:

  • 将版本控制权交还给项目本身
  • 避免 “幽灵依赖” 式的隐式依赖关系
  • 消除因传递依赖升级带来的不可预测风险
  • 更符合现代包管理最佳实践(更小的 Bundle 、更好的 Tree-Shaking )
  • 让库职责更加单一清晰

注:“幽灵依赖(phantom dependency)”指代码运行时依赖某个包,但该包未在当前项目的 package.json 中显式声明。这种情况通常源于 Node.js 的模块解析机制能够访问 node_modules 中的任意已安装包,从而导致依赖关系在项目层面不可见。

关于样式加载方式的变更

和 v0.x 不同,从 1.x 开始不再自动注入样式。

在 v0.x 中,样式会被打包为字符串,并在组件加载时动态创建 <style> 标签插入到页面中,例如:

// v0.x 时的源代码设计
import { loadRes } from '@bassist/utils'
import cropperStyle from 'cropperjs/dist/cropper.css?inline'
import vpcStyle from './style.css?inline'

loadRes({
  type: 'style',
  id: 'cropperjs',
  resource: cropperStyle,
})

loadRes({
  type: 'style',
  id: 'vue-picture-cropper',
  resource: vpcStyle,
})

从 1.x 开始,需要主动在项目的入口文件导入组件样式:

// 此处是业务项目,需要主动导入 Cropper.js 样式和 VuePictureCropper 样式
import 'cropperjs/dist/cropper.css'
import 'vue-picture-cropper/style.css'

为什么移除自动注入?

自动注入样式在早期虽然方便,但也带来一些问题:

  • 可控性低:样式在组件加载时才插入,难以统一管理和覆盖全局主题
  • Tree-Shaking 无效:打包时无法有效剔除未使用的样式
  • 冲突与重复:在大型工程或多实例场景下,可能会重复注入或覆盖其他样式
  • 一致性问题:SSR、CSS Modules 或其他构建工具环境中,动态注入样式可能导致渲染不一致

改为显式导入后:

  • 项目可以统一管理样式入口
  • 支持 Tree-Shaking 和按需加载
  • 与现代前端构建工具和工程实践高度兼容

在 1.x ,样式加载更透明、可控、可维护,符合现代前端工程化最佳实践。

关于实例的获取方式调整

在 v0.x 版本中,cropper 实例是通过模块级变量管理的:

// 0.x 源码设计
export let cropper: CropperInstance | null

// 使用 0.x 业务
import VuePictureCropper, { cropper } from 'vue-picture-cropper'

这种设计带来了一些限制:

  • 多实例冲突:同一页面同时使用多个裁剪框时,必须额外包一层子组件,否则实例会互相覆盖。
  • 逻辑复用不直观:开发者在多实例场景下调用方法或复用逻辑时不够方便,使用体验受限。

因此 1.x 完全重构了实例管理方式:

  • 组件实例独立管理 Cropper

    每个 VuePictureCropper 组件拥有自己的状态,不再依赖模块级变量,保证实例相互独立。

  • 通过组件 ref 直接获取实例

    开发者可以安全地调用 getDataURL、getBlob、getFile 等方法,无需担心覆盖或冲突。

  • 支持多实例和逻辑复用更简单

    在同一页面中同时存在多个裁剪框,各实例互不干扰,操作逻辑更清晰、可复用性更高。

如果希望在纯逻辑中使用裁剪能力,而不依赖模板 ref,可以使用 1.x 提供的组合式函数 useCropper,直接获得与实例绑定的控制器,实现逻辑层面的复用和集中管理。

写在最后

总的来说,1.x 在 Bundle、样式和实例三方面做了统一取舍:依赖与样式由使用方显式管理,实例与组件一一对应。这样既便于构建与 SSR,又支持多实例与逻辑复用。

虽然是一个 Breaking Change ,但 1.x 的 VuePictureCropper 组件 Props 仍然保持和 0.x 一致,主要有以下不同点:

  1. 依赖与版本变化
  2. 组件内置样式的载入方式变化
  3. 获取 Cropper 实例的方式变化

具体迁移步骤请参考 从 v0.x 迁移 以及 在线示例

年终总结:2025 年的一些回顾和 2026 年的一些小规划

2025 年过得真是快,感觉比以往都要快。

开源社区与 AI 编程

梳理了这一年的变化,还是先从开源说起吧,今年在 GitHub 上的活跃度,勉强保持了打卡式活跃……

2025 年的活跃情况

搞错了,这是黄村地铁站的 Commit ……

广州 4 号线黄村地铁站的提交墙啊哈哈哈

哈哈哈哈哈拍摄于 2025-12-30 晚上下班后,估计有人看到我在那拍,毕竟那是个人来人往的换乘站,还是在人最多的拐弯处,大家都在赶路,就我突然站在那对着墙拍照!

我的活跃在这里,截图还是生成自 GitHub Contributions ,用了好几年了这个工具。

这两年在 GitHub 的活跃情况

活跃度断崖式下降,有效的新开源工具 0 ,新项目 0 。

因为 AI 辅助编程的发力,从三月份开通 Cursor Pro 开始,加上后面交叉使用的 GPT 、 Claude 、Gemini …… ,几乎全年都在 Vibe Coding ,日常已经转为一名 Prompt 工程师……

Cursor 的 Token 消耗量是 603.1M

AI 发力带来的好处就是提高了生产力,解放了劳动力;带来的副作用就是好像一瞬间失去了想做点什么的动力,好像什么都能用 AI 搞,好像什么都没必要分享了,都是问一下 AI 就行。

所以今年一直只是在私有仓库里尝试一些乱七八糟的东西,或者写点私有笔记,真正拿来搞点什么东西分享到开元社区的,没有,下次一定!

这一年的工作

在去年 2024 的年终总结 里提到公司的主力项目今年要 Release ,做到了,包括相关的硬件产品也推出了几个比较基础的型号,市场反馈和用户反馈还可以,这一年的付出还是得到了肯定。

年底公司也组织了一次团建,那天上午还开了个总结会,然后分组讨论讨论讨论着被我们产品妹子推上去讲了两句,留了张年度照片(就是旁边玩手机这个人……),哈哈哈哈我就不露脸了,露个文身看得出是我就行。

广州真热啊,12 月我还短裤短袖

前面说到在开源一点都没产出,不过在公司里产出还算可以吧,AI 解放了双手,有更多的时间去思考和沉淀,留下了几十篇技术文档,完成的需求功能也不算少,几乎每个版本都有自己实现的东西,做的东西有人用,不论是开源还是公司产品,这一点都是成就感满满的!

每一篇还挺长的,所以博客也没怎么更新,都写公司内部去了

总的来说工作方面我对自己也是挺满意的,有一些成果算是超出我自己的预期,虽然也依然有一些属于我自己的能力欠缺以及业务痛点还需要解决。

这一年公司也入职了很多技术超级无敌强的大佬,性格也都很好,很务实很沉稳,能一起共事真是十分荣幸,新的一年公司产品线也还会继续壮大,应该也是继续很忙,下一年的总结应该会更好。

另外,还有了一件很有纪念意义的工作服,设计灵感来自我们的系统界面,笑死,以后写 BUG 小心点,不然穿出去被人追着打...

专属工作服

OS 在控制台的界面

关于生活

这一年依然选择了宅了一年,哪也没去玩,不过翻阅过去一年的朋友圈,也是有一些有意思的事情发生。

简单摘录一点,也不放太多哈哈哈哈,不然就是流水账了!

加了个文身

六月份终于把三年前就想纹的图案构思了出来,两株麦穗,源自广州的别名 “穗城” 和她的 “五羊衔谷” 传说,从我的贝斯两侧缓缓伸出,绕肩而上,同根生长。

这个图案名为《同根》,其实也是我最喜欢的吉他手黄贯中的一张专辑名称,同根 也是这张专辑的同名主打歌曲,歌曲的创作与饥荒和互助有关,这个文身的出处也是。

另外对于我来说,还有另外一层意义,广州这座城市在我年龄还是个位数的时候,浓厚的历史沉淀就对我有着精神上的吸引,长大后在这里读书再到出社会独立,又从物质上帮我扎下了根,直到现在依然喜欢这座城市。

文身那天发的朋友圈

现在我的右手是这样

全部文身可以在 文身专栏 找到,这里记载了每个文身的意义。

重新设计了博客

四月份那会想重新设计博客首页,给博客找了好久 Hero 区域的素材搭配,最终敲定的方案又回到 “最好的素材就在身边” 的原则,用自己的第一个文身。

如今也快过去一年了,还是特别喜欢,越看越喜欢,包括我的手机壁纸也一直是这个文身。

手机壁纸

文身专栏

重新设计博客的时候,也顺带设计了我的文身专栏,第一次这么正式向大家介绍我的文身和它们背后的故事,以及合作了十年的纹身师!

再贴下传送门:文身专栏

我的文身和它们背后的故事

手写的贺卡

一对一轻量级资助了个山区小女生一点点费用,竟然收到教育局寄来的小朋友手写贺卡,看到这个涂改液,小时候自己涂改作业的事情回忆起来真美好!云养女儿的感觉!

小朋友的手写贺卡

放大看!这个涂改液太有回忆了

还没全秃

虽然这两年明显感觉到发量在变少,但还好还没全秃,而且有时候起床后蓬松感还不错!

要是哪天快秃了,我就去搞个脏辫,脏辫玩够了就剃光头去!

蓬松感带来一天的好心情

发发发

原来真有人随机到 888 的咖啡订单号码,是谁?是我!

发发发

让摇滚的声音响彻整个夜晚

去年的总结《本色十年》回忆了十年来的一些工作与生活变化,包括转岗的坚持等一些心路历程。

如今在 2025 年往前看十年,其实 2015 年也是挺有意义的一年,那一年鼓起勇气入职了网易,度过了对我人生十分重要的五年,后来离职的时候还写了一篇《让摇滚的声音响彻整个夜晚》记录我在网易五年里的工作与生活,都是特别美好的回忆!

接下来是 2026 年了,也开始踏上了我文身十年,以及养猫十年的时间点,回头看过去,人生的每一步不在于是否都能踩对,但如果能留下一些有意思的事情,每次往回看十年前,依然能开心,这辈子也就值了。

新的一年

回到最开始,之所以先说开源和 AI ,就是想说我这一年在工作之外其实是有点缺少目标感,说白了就是迷茫。

尽管也学了不少新东西,在工作上也用得游刃有余,但就是总觉得没什么很垂直方向的沉淀,大部分时间都在创造当前的价值优先,缺少一点可持续发展的安全感。

特别是负责了一个很需要专业知识的项目也有一年多了,虽然目前也运行的还行,有吐槽,但不算多,但始终还是处于赶需求赶需求的状态,大部分问题和实现都懂,但在这个领域还是缺少更多的专业知识学习和实际探索,时间不够用。

上一个 OKR 周期在向老板复盘汇报的时候我也说,我始终不敢说这个项目是我在负责,因为我觉得还没有做到我自己很满意的程度,没脸公开啊哈哈,只能做一个默默维护的神秘客。

前两年的总结没有定下什么目标,今年希望能安排好自己的时间去钻研下,最好也仍然能沉淀一些自己的经验记录。

另外也减少点有的没的时间浪费,新东西层出不穷,倒也没必要到处尝试,今年就是一个浪费时间的例子,笑死。

Obsidian x 飞牛 NAS:打造免费的跨平台笔记同步与备份方案

从 2019.08.01 重新开始写日记,到今天居然坚持了 6 年了,一开始是记录在一款云笔记 App 上,直到四个月前陆陆续续把数据迁移到自己家里的 NAS ,把数据爬回来才发现居然接近 8 GB …… 一直觉得好像都是文字为主,没想到也配了不少图片,重新看的时候生活还挺丰富多彩的哈哈哈!

这里的日记就很纯粹的记录生活,不是什么读书笔记、技术笔记等等,那一类的记录对我来说都属于生活之外的事情,冷冰冰没有个人感情,生活日记记录了我每一段生活的喜怒哀乐,还有各种胡思乱想,闲着无聊的时候翻一翻,能够回顾自己过往的生活和成长,别有一番乐趣。

哈哈哈很多都是跟猫生活的美好瞬间

早期的方案

2019 年重新开始写日记那会,我当时的需求主要是晚上睡觉前用手机记录当天的日记,并且有网页版或者桌面客户端可以平时在电脑上看一看稍微管理一下就足够,但那个时候还没什么特别流行的跨平台笔记方案,而且大部分流行的笔记 App 都是广告很多。

加上当时还没有开始接触 NAS ,也没有选择自建存储的想法,后面选择了一款界面比较清爽、自我感觉用户体量比较小的 App(猜的…… ),由于那家公司本职不是做这个笔记 App 产品,所以对 App 利益相关的运营干涉不是太多,所以 “清爽的体验” 一用就这么用了五年多。

但随着长时间深入使用,这也变成了我不想用它的原因,由于 App 不够被重视,以至于多年未维护更新,产生的 BUG 也没有修复,一度让我自己想抽空做一个客户端自己用(根据它的 BUG 表现推测是比较老版本的 React Native 做的)。

这款 App 的用户体验让我越来越难以忍受

新的迁移方案

不过由于工作太忙, “做一个情怀客户端自己用” 这个事情也就一直放着,可以看到我是 23 年开的 Project ,直到现在都没动哈哈哈。

不过拖延症有拖延症的好处,因为这几年陆陆续续听到 Obsidian 、 Logseq 、 Joplin 、 Notion ,以及国产的为知笔记、思源笔记,以及语雀、飞书文档这一类不太纯笔记但也提供了很优秀的笔记功能的产品,有这么多现成的,干嘛还自己搞呢?

而且在开 Project 的那段时间,也开始玩起了 NAS (详见 我的第一台 NAS 一文),玩熟悉之后,用 NAS 来存储这些相对敏感的数据,应该说是最好的选择了。

迁移之前的考虑

有了数据迁移的想法,先梳理看看自己都需要些啥功能。

明确自己的需求

在现阶段,用 NAS 作为数据存储是已经确定的事情,剩下的不确定因素主要还是客户端方案的选择。

需求点 说明
私有化部署 考虑到自己当前的需求主要是个人日记,偏隐私,我选择将数据存放到我的 NAS 上
多平台客户端 我自己主要设备是需要有 iOS App 和 macOS App (或者 Web )
客户端响应快 不论是启动速度,还是搜索速度,因为写了 6 年,几千篇笔记,速度方面还是有要求的
交互体验好 想换掉之前的方案就是因为体验太差了,至少不能再有我上面提到的那些问题
界面颜值高 我当时选那个 App 的原因就是清爽,虽然没有暗模式,但亮模式的 UI 很像 Shadcn UI
多端同步 最重要的功能,可以直接与我的 NAS 进行数据传输

客户端之间的选择

支持私有部署的 “单机笔记” 方面,主流的就是 Obsidian 、 Logseq 、 Joplin 、为知笔记、思源笔记 这几款。

基于社区评价、私有化部署的内存占用( NAS 比较重视)、客户端颜值等维度对比后,就剩下 Obsidian 和 Logseq 两者, Logseq 是比 Obsidian 要晚一点推出的产品,所以它具备了 OB 的一些优点,同时还具有双向链接、块引用等更现代化的功能。

不过社区普遍认为 Logseq 在处理大量笔记时性能稍弱一些,而它基于大纲的组织方式,对我目前以 “日记” 为主的使用场景来说略显复杂。

未来如果要记录其他类型的内容,我可能会选择 Logseq 来做区分,但目前还是选了更为经典的 Obsidian 。

目前的方案

接下来讲讲我目前确定下来的具体架构和配置。

多端同步架构

经过前面的方案对比之后,我的笔记方案整体架构非常简单。

  • NAS 作为数据存储中心,并通过自带的 WebDAV 服务对外同步
  • 多端则使用 Obsidian 官方提供的桌面和手机客户端,配好对应的同步功能即可和 NAS 进行数据对接

以 NAS 为中心的架构图

在同步方案的选择上,基于 NAS ,就毫无悬念选 WebDAV 了:

  • ✅ 数据完全私有,不依赖第三方服务
  • ✅ 局域网内同步速度极快,也可以配合 DDNS 或内网穿透实现外网访问
  • ✅ 理论上无限容量,无需额外的订阅成本(硬盘成本算到 NAS 的购买成本里了)
  • ✅ Obsidian 有对应的插件支持 WebDAV

其他方案对比

不过 Obsidian 也有其他的同步方案,这里也顺便记录下查过的方案对比,供参考:

方案 优点 缺点
Obsidian Sync 官方方案,稳定可靠 需要订阅,最低 $4 / 月,并且限制 1 GB 上限,单个文件不能超过 5 MB
iCloud Drive 苹果生态无缝集成,有基础的免费容量 真正用起来的话需要订阅,最低 ¥6 / 月可以达到 50 GB ,否则只有 5 GB 可用,另外注意这些方便服务仅限苹果设备
网盘 主流云盘,稳定,有基础的免费容量 免费版速度慢,容量小,也是需要订阅提升体验,但依然有容量限制、文件大小限制
Git 同步 免费,版本控制强大,文本 Diff 对比速度快 配置复杂,不适合非技术用户;仅对纯文本友好,不适合托管图片、视频多的笔记内容
WebDAV (NAS) 服务免费,内置服务开箱即用,局域网速度超快 需要 NAS 设备(一次性硬件投入)

飞牛同步客户端

在这里还要提及一个特别的同步方案,那就是飞牛同步,支持 Windows 和 macOS 平台。

飞牛同步的优势在于:

  • ✅ 飞牛官方团队维护,与飞牛 NAS 深度集成
  • ✅ 配置相对简单,对非技术用户更友好
  • ✅ 针对飞牛 NAS 优化,同步性能可能更好

可以在官网下载 飞牛同步客户端

飞牛同步客户端登录界面

不过目前飞牛同步还只有 PC 版本,还没有移动端版本支持。对于我这种需要在 iPhone 上记录日记的场景,WebDAV 方案配合 Obsidian App 是更合适的选择。

但如果你的主要使用场景是桌面端同步文件(不限于 Obsidian ),或者希望有更简单的配置流程,飞牛同步也是一个值得考虑的选择。

使用体验

先说说目前方案的使用体验吧,如果你觉得这套方式也适合自己,再继续往下看配置部分。

旧数据迁移

我之前用的那个笔记 App 不支持导出数据,所以我是通过 DevTools 查看它的 API 请求过程,写了个爬虫把笔记内容爬了回来。

对方的 API 返回的是 HTML ,因此本地又编写了一个 HTML 转 Markdown 的工具进行格式转换(推荐 Remark 系列工具包)。

笔记中的图片原本也是远程 URL ,我在爬取时一并下载到本地,并按日期文件夹归档,再将笔记里的引用路径改成相对路径指向本地图片,当然这些工作也是用脚本处理的。

具体细节这里就不展开了,前端同学对这种流程应该不陌生,而且爬虫这东西也不太方便公开细说。

使用感受

写这篇博客时,我已经迁移到 Obsidian + 飞牛 NAS 四个月了,总体体验可以说是非常满意:

优点:

  • 速度快:局域网同步几乎是秒传,搜索 2000+ 笔记也很流畅
  • 稳定性好:至今未出现同步失败或数据丢失
  • 界面清爽:Obsidian 的界面简洁干净,用起来非常舒适
  • 插件丰富:几乎所有功能都有对应插件支持自定义
  • 数据安全:可以只在局域网内同步,数据不出网更安全
  • 备份方便:在 NAS 端还可以通过 “文件备份” 功能自动备份到其他存储位置

需要注意的点:

  • ⚠️ 初次配置有门槛:NAS 端配置简单,但 Obsidian 的客户端选项较多,需要点时间摸索
  • ⚠️ 外网访问速度:若 NAS 没有公网 IP ,使用 FN Connect 免费版的访问速度可能偏慢(可考虑付费版)
  • ⚠️ 移动端容量:Obsidian 是本地化编辑器,所有文件都会存储在设备上,需确保手机空间充足
  • ⚠️ 预防同步冲突:多设备同时编辑时,仍需留意冲突问题

多端同步配置

接下来讲讲怎么围绕 NAS 这个数据中心实现多端同步,主要以飞牛 NAS 端,以及 Obsidian 桌面客户端,先把流程跑通了,在 Obsidian App 的设置也是一样的。

飞牛 NAS 配置

下面的配置步骤都是以 Web 端的操作为例,在飞牛的 App 操作也是类似,按顺序操作即可,需要注意的是,请使用管理员账号 登录飞牛 NAS ,而不要使用普通账号,很多操作需要管理员才可以设置。

创建笔记存储目录

建议在 “文件管理” 中创建一个专门的文件夹,作为数据的存储根目录,比如 database ,这样其他类似的数据托管都可以存档在该文件夹里。

真正存放数据的地方,可以根据需要再建一层目录,例如我的日记是放在 databasediary 下。

重要数据建议存放在一个有数据保护的存储空间下,预算不高的话可以像我一样,用两块 2TB 的硬盘创建一个 RAID 1 。

创建数据根目录

配置 WebDAV 服务

飞牛 NAS 自带 WebDAV 服务,访问 “系统设置 → 文件共享协议 → WebDAV ” ,启用服务。默认的 HTTP 端口号 5005 / HTTPS 端口号 5006 可以直接使用,也可以自行修改。

再点击 WebDAV 界面上的 “设置可见文件夹范围” ,设置允许通过 WebDAV 访问的目录范围,根据自己的需要去限制范围,记得包含刚才的数据文件夹就行。

在 NAS 的 WebDAV 配置界面上,可以看到自己的访问地址是

  • http://{你的 NAS 内网 IP}:{端口号}/
  • http://{你的 NAS 域名}:{端口号}/

注意,使用 HTTP 或 HTTPS 时,两者的端口号是不一样的

如果需要使用域名访问 WebDAV ,可以在 “系统设置 → 远程访问” 管理 FN Connect 账号,或者在 DDNS 配置域名,具体在这里不过多介绍,飞牛的界面操作还是很清晰的。

系统设置 - 文件共享协议 - WebDAV

Obsidian 桌面客户端配置

在 Obsidian 中,根据最流行的免费同步方案,使用了 Remotely Save ,另外为了统一处理 Markdown 的内嵌文件路径(图片、视频等)的存放位置,我同时使用了 Custom Attachment Location 插件。

仓库与日记设置

启动 Obsidian 后会引导创建一个笔记仓库,其实就是在电脑里选择一个文件夹存档这些笔记,所选的文件夹对于这个仓库来说也是一个根目录的概念。

如果和我一样是用来写日记的,或者是想类似日记一样在同一个文件夹里存档笔记,并且有自己的固定笔记模板,那么可以在 “日记” 设置一些存档规则,例如我选择了将所有日记都归类到 docs 文件夹,而日记模板则归类在 template 文件夹下(模版需要具体到某一个文件的路径)。

在 Obsidian 的日记设置界面,以及 Mac 对应的目录和文件

内部链接设置

Obsidian 对 Markdown 的链接和图片引用默认是它自己的语法,为了以后兼容其他客户端,建议这里也改成 “基于当前笔记的相对路径” 。

将内部链接规则修改为 ”基于当前笔记的相对路径“

安装插件

插件好像都是从 GitHub 安装的,所以需要确保所处的网络环境可以顺利打开 GitHub 。

  1. 打开 Obsidian 设置 → 第三方插件 → 关闭 “安全模式”
  2. 点击「浏览」搜索 Remotely Save (必要)和 Custom Attachment Location(可选)
  3. 安装对应的插件并启用

同步插件配置

同步插件是核心插件,使用的是 Remotely Save ,这里有几项需要设置:

设置项 如何设置
远程服务 选择 WebDAV
服务器地址 从飞牛 NAS 复制 WebDAV 的访问地址,拼接数据库文件夹路径,例如 http://192.168.8.8:5005/database
用户名 飞牛 NAS 里,这个 database 文件夹的归属账号的用户名
密码 飞牛 NAS 里,这个 database 文件夹的归属账号的密码
并行度 由于我只使用局域网同步,所以我开到了最大,目前可以设置 20

其他的就根据实际需要调整,或者保持默认就可以了。

Remotely Save

附件插件配置

辅助插件目前只用了一个附件管理的 Custom Attachment Location ,从前面的仓库与日记设置可以看到我还有一个 assets 文件夹,这是因为我每天的日记除了文字,还带有不少图片,有时候还会贴视频,如果没有合理归档这些附件,混在一起就太难维护了。

设置项 如何设置
Location for new attachments 我配置了 Location for new attachments../assets/${noteFileName} ,这样每一篇日记的附件都会归档到 assets 文件夹下的 “笔记文件名” 文件夹里
Should rename attachment folder 如果笔记对应的 Markdown 文件修改了命名,它会监听并重命名这个附件文件夹,虽然我几乎不改,但一旦修改,这个功能确实挺省事的

其他选项可以根据自己需要修改,例如图片自动重命名的一些功能。

主要设置 Location for new attachments 选项

Obsidian 移动设备配置

我的常用移动设备是 iPhone ,所以直接在 App Store 搜索 Obsidian 即可找到客户端下载,也可以在官网找到其他端的下载。

安装好 App 后,在 iPhone 或其他设备上重复上述步骤进行配置。

总结

日常写笔记时,我的习惯是只在一端更新,写完后同步回 NAS ,下次在另一台设备启动 Obsidian 时,插件会自动比对版本,从 NAS 拉取最新数据,实现双向同步,这种方式在实际使用中没有出现文件冲突,整体体验非常稳定。

从 2019 年开始在第三方 App 上写日记,到现在用 Obsidian + 飞牛 NAS 搭建私有笔记系统,这 6 年的数据迁移总算告一段落。

这套方案在技术上并不复杂,但在内容管理理念上是一次很大的升级:

  • 数据完全自控,还可以在 NAS 上定期备份或迁移
  • 所有数据本地均有存档,离线也能完整访问与编辑
  • Markdown 格式通用、可扩展,不受笔记客户端约束

经过几个月使用,无论是多端同步、搜索速度还是文件安全,都比以往的云笔记方案更可靠,如果你也希望让笔记系统更可控、更长期可维护,这套组合值得一试。

记录一次 ERR_INCOMPLETE_CHUNKED_ENCODING 的问题排查

最近博客改版也顺便改了部署方式,页面访问也检查了重定向配置等等,看起来似乎没什么问题,但还是收到了一个反馈 RSS 订阅源报错的情况( issue 见 #370 ,订阅源见 feed.xml ,感谢 @AsanZhang 的反馈 )。

反馈在 RSS 聚合软件里提示订阅报错了,我自己也尝试了确实不行,奇了怪了!

在 RSS 聚合软件里提示订阅报错了

报错截图和信息

在浏览器直接访问 XML ,发现 Network 里 Failed 了,控制台还报了个错误信息:

net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)

截图如下:

请求状态

控制台的错误信息

这个报错有点眼熟啊!想起前段时间给博客加搜索功能的时候,一开始想做全文搜索,结果部署后也遇到类似的报错,本地 build 完预览没事,线上就跪了。

之前做搜索的时候也遇到过类似错误

但那次因为是把所有文章都处理到一个 JSON 文件里,但因为文章里有很多代码块等内容,引起问题的原因比较多,例如可能破坏了数据结构、文件本身也很大,所以做搜索的时候最后决定去掉全文,改成了只搜标题,解决了当时遇到的问题,但没想到在 RSS 这里还是遇到类似的情况了。

那会还在本地 Docker 部署对比了,但本地也正常,愣是没怀疑到线上多了一层 Nginx 可能是个坑。

解决思路

由于对 Nginx 并没有过多的深入使用,常年处于基础的转发配置阶段,所以直接请教 GPT 帮我解决。

ChatGPT 给我的解决思路

调整 Nginx 的配置

原来的配置是这样子,比较早期的默认配置:

server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 50m;

sendfile   on;
tcp_nopush on;

keepalive_timeout 60;

tcp_nodelay on;

fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 256k;

按照 GPT 的描述, proxy_buffer_sizeproxy_buffersproxy_busy_buffers_size 这些参数用于调整代理缓冲区的大小, fastcgi_buffer_sizefastcgi_buffersfastcgi_busy_buffers_size 这些参数用于调整 FastCGI 缓冲区的大小,另外还建议我新增 Proxy 缓冲区相关配置。

打开 nginx.conf 文件,修改配置如下:

fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
-fastcgi_buffer_size 64k;
+fastcgi_buffer_size 128k;
-fastcgi_buffers 4 64k;
+fastcgi_buffers 4 128k;
-fastcgi_busy_buffers_size 128k;
+fastcgi_busy_buffers_size 256k;
-fastcgi_temp_file_write_size 256k;
+fastcgi_temp_file_write_size 512k;

+proxy_buffer_size 128k;
+proxy_buffers 8 128k;
+proxy_busy_buffers_size 256k;
+proxy_temp_file_write_size 512k;

调整完这些配置后,重启 Nginx 服务以应用更改。

sudo nginx -s reload

现在确实解决了,成功订阅!

成功订阅!

年终总结:2023 年的一些回顾和 2024 年的一些小规划

我家三只超级乖的猫

在落笔之前想起一个古早笑话……

快六点了,街边卖油条的还没来,我只能拨通他的电话,大哥在那边说:我卖了这么多年的油条了,从来自由自在的,自从遇见你之后,我居然有了上班的感觉。

因为我被催更了哈哈哈哈哈(见 #363 )。

催更现场

本来今年的总结第一句话想写 “2023 对我算是比较特殊的一年” ,结果看了一眼去年的回顾,去年也是这么写的…… 那今年再强化一下,改成 “2023 对我是非常特殊的一年” 哈哈哈哈哈哈哈,确实非常特殊的一年,因为经历了很多之前没有经历过的事情。

人生中的第一次失业

去年写总结的时候还是元旦放假前,当时的我对于三天后会发生什么事还一无所知,但也不能说完全一无所知吧,作为一名程序员和刑侦爱好者,上家公司总是结合疫情的风吹草动安排半薪和基薪休假(广州当时其实就海珠区比较严重,其他区域已经放开,我们都不在海珠区,这么喜欢跟风就很不对劲……),所以就已经感觉到前公司当时的运作要崩溃了,只是没想到来的这么快而已。那时候元旦假期刚过完,回去上班第一天前老板就开会说融资失败要执笠了,团队要解散。于是我刚休完元旦假期,上了一天班又继续放假了,当然这次是无薪假期哈哈哈哈。

这是我人生中第一次在没下家的情况下直接失业,既然前面有所感知,为什么没有骑驴找马呢?因为团队里都是我合作了很多年的 Partners ,都是我在网易从 2016 年起陆续认识,一起玩了六七年的 Friends 和 Leader ,包括从网易当时的总监熬不住先跳槽了,我们一起被合并到边缘部门,再一起离职去深圳待了两年,再一起回广州,经历了很多事情,所以表面上连网易在内到当时失业,经历的是三家公司,但实际上一直都在一个团队里(包括前老板以前也是网易的总监,所以那段时间首先想的是有难同当,低谷期总是有的,熬过去就好了)。

过年前失业确实是一个很尴尬的节点,大部分公司在年底是没有招聘计划的,通常会在二月底到三月,等新一年的目标定好才会开始陆续招人,所以那个时候想走内推也没有什么公司有 HC ,也尝试谈了几家创业公司,也没有那么大的团队缺口,都是最多只要一个经验丰富点的来带头开荒就顶天了,盘不下几个人的团队一起去,加上做的事情比较无聊和薪资没谈拢,干脆就等过完年再看机会。

失业期间在干什么

虽然人到中年还是 “三无” 选手(无老婆、无小孩、无房贷压力),但这个时候 “三无” 属性反而使我的抗风险能力达到了 Max ,没有银行给我的还款压力,没有人在旁边唠叨给我精神压力,身边还有三只懂事粘人的猫,所以那段时间心态也比较平稳,工作方面没了事情做就没事情做吧,我自己还有很多事情要搞(在 GitHub 上列了一堆 Projects ),还是忙个不停。

改稿子

那个时候已经和出版社签了合同,准备出版我的书,基于出版规范很多细节要调整,包括不允许出现 “你我他” 这些人称代词,还有很多描述不能口语化,更重要的是保证代码的准确性,平时苦于每天在睡前挤那一两个小时在开夜车改稿,一下子突然有了很多时间让我慢慢改,反而有一种莫名其妙的幸福感…… 每天终于不用那么累了,于是对着 83 页 PDF 的规范手册,几十万字的稿子硬是改完了。

这是后来寄过来校对第二次的清样,哈哈哈哈也别被吓到,单面印刷的所以看起来厚到吓人。

薄如蝉翼的稿袋

也就几页稿子…

后来这本书在五一期间正式上市了,围绕前端工程化的入门,虽说前端娱乐圈,框架一个又一个,但真正被企业认可的以及基础方面的知识主要还是没太大变化,书的内容的有效性应该可以维持上几年,刚入门前端觉得有需要的同学可以支持一下,感谢您!

《前端工程化:基于 Vue.js 3.0 的设计与实践》

相关阅读:我写了一本书《前端工程化:基于 Vue.js 3.0 的设计与实践》 想分享一下它背后的故事

当时给身边好友写的签名本,好羞涩哈哈哈哈哈。

人生第一次在自己的书上签名

真的很羞涩哈哈哈哈

做设计

去年的总结说要对 create-preset 改版,程序至今还是没时间改,倒是对官网先重新设计了一版,还自己鼠绘了个 LOGO ,一年了,自己看起来还是很满意哈哈哈哈哈哈。

当时的票圈

还想设计设计自己的新文身,从去年回广州就想加图案,但因为忙也没时间搞,有时间的时候也会胡乱画画找找灵感(红色的是画上去的,最终还是没确定下来,后面也没有时间了就又搁置了,新的一年再看看吧)。

乱画一通

第一个文身的由来

戒掉了网络游戏

当年作为一个资深玩家进的游戏行业,又凭着丰富的游戏经验跳去了网易游戏,没想到有一天我也会不玩网络游戏了,一月份我弃坑了玩了五年的某手游(排名在全区第二),一方面是失业带来的危机感让我觉得继续玩这种时不时要充值的游戏没什么意义,另外一方面是狗策划安排的活动时间实在是过于挤压我为数不多的休息时间,既然花了钱去玩还得不到快乐,那就干脆就不玩了。

在成为 “不抽烟不喝酒不玩游戏” 的好男人一段时间后,有时候很无聊又想玩玩游戏消磨下疲劳,所以现在又像小孩子一样,重新玩起了小时候玩的那些游戏,在模拟器玩 FC 和街机,还有老头滚动条之类的经典单机。

红白机永远的神!

双截龙 Ⅱ

其他的事情

其他事情主要就是改简历和准备面试的事情,失业时间说短不短,说长也不长,刚好过年前到元宵后休息了两个月,二月底内推到现在的这家公司面试通过后就入职了。

新工作是干什么的

现在在一家做 NAS 方向的初创公司做开发,依然是自研团队。目前项目里 C++ 负责 NAS 操作系统的开发,而前端这边是负责把操作系统上的功能实现为界面化的交互(像 Windows / macOS 那样),所以这是一个很重前端的项目,比起传统的 C 端应用或者 B 端一些低代码平台,这个项目我觉得更有意思,也很考验开发者的 TypeScript 功底,目前来了半年多,感觉自己的能力和理解又有了很多提升,当然也离不开身边大佬们的指导和帮助。

产品还在开发中,等上线了我再公开哈哈哈哈

另外还有一个很神奇的缘分,就是现在的老板其实也是我十年前的老板, PP 助手的创始人(早期越狱那个年代的 iOS 用户应该都知道?),我之前也是在 PP 助手团队,后来跟着被收购到了 UC ,再被收购到阿里,被阿里收购后因为阿里部门间合并后的一些后遗症,做的不开心了,我才跳去了网易。

UC 十周年时,当年发的朋友圈

兜兜转转又回来了以前的团队,说不出的兴奋感,因为这里的人也很好,又是一见如故,哪怕多年未见,连我的花名都还记得!虽然离开了一群合作很久的 Partners ,但又回到了另一个很好的 Team ,很开心的一年!现在这里除了以前 PP 的老友,也有后面在 UC 和阿里的同事,虽然有些之前并未谋面,但很快就达成了默契,很合拍的工作节奏!

开源社区

今年还是活跃在 GitHub 上(截图生成自 GitHub Contributions ),可能去年码字太多,今年几乎没写什么文章,一直在敲代码,弥补之前没做的很多事情。

2023 年在 GitHub 的活跃情况

今年敲代码的时间比去年高了不少,晒一下这一年最高敲代码时间的一周,沉浸式敲代码哇哈哈…… (统计数据来自 WakaTime ),我记得有一天敲了 12h 的代码,太早的统计要付费才能看,忘记留个图纪念下。

2023-12-03

这么长的敲代码时间倒不是因为公司加班,公司除了版本 DDL 前后会加加班之外,都是六点半下班和双休。

时间方面主要是在这几个地方:

  1. 完善自己的工具链( e.g. 贝斯手 Bassist 系列,还有一些没公开的),把自己常用的工具和配置都集合到一个 Monorepo 管理,避免每次用的时候都要加一堆配置或者重新实现
  2. 踩 React 的坑……(下面说)
  3. 踩其他技术栈的坑(例如尝试写了一点 Rust …… 还有各种 demo 吧,不是开源,但也都托管在 GitHub 上,所以活跃度就这么来的…… )

关于踩 React 的坑,去年在总结的新年展望里提到要好好写一下 React ,倒不是说 Vue 不好,相反,真的把主技术栈切到 React 后才发现 Vue 帮开发者做了好多优化,而 React 好多要靠开发者自己处理…… Vue 真是保姆级!

之所以在出了一本 Vue 的书之后,自己写起了 React ,是因为我写多了想换换口味,之前因为公司一直都是 Vue 为主,所以不知不觉也写了得有五年多的 Vue 了,不想像以前读书一样,只偏科数学和物理,文科一塌糊涂,最后总成绩就显得很一般。目前在 Vue 自己也算是有一套自己的最佳实践,平时遇到问题也可以很快排查,而 React 还没有达到这种状态,所以今年主要也在解决自己的偏科问题。

刚好今年换工作后是新团队新项目,涉及到技术选型,新团队的 Partners 都很一致的选择用 React 来写,所以我也很符合个人预期的都在写 React 了。

其他的感慨

首先, AI 真好用哈哈哈,自从 ChatGPT 出来后,工作再也离不开它了,目前主要用它消耗掉很多基础的工作,比如输出单元测试用例、 JSON 转 TypeScript 类型等等,还有遇到问题现在可能不是首选 Google 搜索了,而是在 GPT 先问一下,如果不靠谱再去 Google / StackOverflow / GitHub Issue 找答案。

再一个就是离开网易后很感慨一件事,就是刚转行的时候曾经的产品运营身份让我在技术路上走的蛮辛苦的,有人不认可,有人不信任,在当时确实是一个劣势,毕竟万事开头难,但经过这么多年的热爱和坚持,劣势反过来已经成为了自己的优势。

有一定的产品设计经验、有一定的用户体验优化能力、懂 SEO 懂运营懂用户,往产品工程师方向发展,比做一个纯技术的全栈工程师更现实一些(相对于我这种普通人的认知层面来说的,大佬肯定又有不一样的认知),当然现在托 Node.js 的福,只写 TypeScript 的我也可以写全栈了。

新年愿望

首先希望身体健康啊哈哈哈,距离上一次去医院不知道多少年了,今年才又进了一次医院,做了人生中的第一个小手术,当时耳朵因为细菌感染引起皮脂腺囊肿,去做了个引流手术,第一次动刀子,内心还是比较慌的,还好过程也没有啥感觉,打了个麻药,所以手术过程中还没有文身有感觉(我文身不打麻药!)

惨兮兮的朋友圈

其他的还是先把原来还没搞完的事情搞起来吧,比如想重构博客一年多了,结果因为这个那个事情,一直拖着没处理,一下子距离上一次重构都已经三年了,真是见鬼啊哈哈哈哈时间怎么这么快!!!

都记录在 GitHub 的 Projects 里管理了,等做到了再说吧,不立那么多 Flag ,免得一年又一年说了又不做哈哈哈哈哈。

好多计划都赶不上变化哈哈哈哈

谢谢阅读!

我写了一本书《前端工程化:基于 Vue.js 3.0 的设计与实践》 想分享一下它背后的故事

大家好,我是程沛权,经过差不多一年时间的打磨和优化,我的第一本技术书籍《前端工程化:基于 Vue.js 3.0 的设计与实践》出版上市啦!

前端工程化:基于 Vue.js 3.0 的设计与实践

这是一本以 Vue.js 的 3.0 版本为核心技术栈,围绕 “前端工程化” 和 TypeScript 的知识点展开讲解的前端入门书籍,主要面向以下读者人群:

  1. 掌握了基础的 HTML 页面编写知识,想学习一个主流前端框架的新手前端工程师
  2. 已经学会了 Vue 2 ,面对 Vue 3 的大版本更新,想快速上手使用的前端工程师
  3. 非职业前端开发,但涉及前端的工作,需要掌握一个主流前端框架的全栈工程师

书里面的知识点是按照工程师做项目的顺序梳理的,比较循序渐进的一个过程,读者可以收获到这些知识:

  1. 了解如何入门前端工程化开发,掌握 Node.js 和 npm 的使用
  2. 掌握前端领域多年来趋势走高、带有类型支持的 TypeScript 语言
  3. 上手主流前端框架 Vue.js 的全新版本,并且在遇到常见问题时知道如何解决

看到这里,我估计有部分读者会觉得眼熟,有熟悉的感觉是对的!因为在它被正式出版之前,有另外一个名字是叫《Vue3 入门指南与实战案例》,最早是部署在我的博客网站上作为本书配套案例和代码资源的 开源版本 分享的。

截止至 2023-05-03 五一假期的最后一天,开源版本已经累计了大约 220 万的阅读人次,受到了不少读者朋友的关注和支持,所以我相信很多人是看过它的在线版本,上面这段话其实就是前言里面的一部分。

今天这篇文章更主要是想分享一下这本书的由来,还有一些关于我、关于写书这些事背后的一些故事,希望感兴趣的读者可以继续支持!

目录详情和购买地址

先放上纸质书的购买地址吧,关于书的目录和内容介绍可以在商品详情查看:

☞ 访问 京东商城 购买

☞ 访问 天猫商城 购买

如果您对我的作品认可,建议购买纸质版,纸质书在电子书的基础上,经过机械工业出版社的编辑老师们的内容优化、校对勘误、排版美化,更成体系,在此特别感谢李晓波编辑对我将开源作品出版为纸质作品的支持,李老师全程帮忙跟进了无数的大事小事,也给我科普了很多出版方面的知识,十分尽职!

这本书怎么样?

在出版纸质书之前,电子版就开通了基于 GitHub Issue 的评论功能,收到了很多读者给我的反馈和交流,摘选了一部分如下(排名不分时间先后):

GitHub 上的读者评论

也有来自宝岛的小姐姐热心安利(我就说某段时间突然有很多来自台湾的工程师关注,然后 Google 了一下发现了这个推,感谢我的热心网友们!

来自宝岛的小姐姐热心安利

因为之前有热心读者问我有没有赞赏渠道,所以我从去年 10 月份在文档上挂了一个赞赏码,可以给我家三只猫猫打赏点罐头,也收到了很多读者的捐赠,有好几笔是大额的赞赏,特别特别感谢!!!

一些赞赏记录

所以关于这本书的内容质量,我相信您看到这里时应该也有了一个大概的了解!

放上我家三只猫,欢迎在线吸猫!

我家的三只猫

我家的三只猫

这本书是怎么写出来的?

作为写了一本大约 32 万字的成品书籍的作者,我一开始并没有很功利的想把它写成一本书,它最早最早的前身其实是我无数日夜在学习过程中的笔记碎片。

刚开始关注和使用 Vue 3 的时候还是 2020 年,那段时间资料很少,相关的官网也只是基于 Vue 2 的内容向上适配了一下,并且只有英文版,其他的随手可得的资料,真的好少好少…… 我那段时间也算处于一个比较早期的开荒阶段,遇到问题也只能 Google 和 StackOverflow ,还有在 GitHub 仓库的 Issue 区和源码挖挖看有没有解决方案。

再后来发现还是不太够用,慢慢地又跑去 RFC 仓库 挖掘一些还没有正式公开,但其实已经实现的 “隐藏功能” ,期间也整理过一些文章分享出来过(例如: Vue3.0 最新动态:script-setup 定稿 部分实验性 API 将弃用 , Evan 大佬还在评论区亲自帮忙解答大家的问题)。

因为开荒的过程都是利用本就不多的休息时间,所以早期为了解决各种问题和先把项目做出来,也没有第一时间整理博客笔记,我通常的习惯都是先把找到的资料丢给我一个专门存档临时笔记的微信小号,作为一个可检索的 “便签” 。

当时记录的一些临时笔记

再后来发现实在太多了,这么零散的记录我自己回头也容易忘记,秉着对费曼学习法的多次实践(用输出来倒逼输入,真的特别有效!),逐步整理出了第一个版本,在 2020 年国庆节那会部署到了博客上面,刚上线的时候它还只是一本很纯粹的关于 Vue 3 的入门学习指南,内容比较单一,阅读门槛也比较高(需要本身已经熟悉了 Vue 2 才能看懂我在说什么)。

很感谢第一波读者的鼓励,很热心地给了我评论反馈,还收到了很多邮件交流遇到的问题,经过不断地迭代,慢慢地补充了很多通俗易懂的例子,并且逐步增加了关于前端工程化和 TypeScript 的入门学习内容。

期间还有很多读者自发帮我宣传( e.g. PR #6075 ),让我写的内容逐步被更多的人看到,所以到了后面一共有三家出版社联系了我出版事宜,也才有了后面这些出版相关的事情(因为收到不同出版社的邀请在时间上跨度比较大,所以我也是选择了最早联系我的李晓波编辑和机械工业出版社,在这里也很感谢其他出版社的编辑对我的作品的认可!)。

我是一个什么样的人?

再来说说我自己,我并不是一个很纯粹的程序员。

认识我久一点的朋友也知道我并不是从毕业就一直在做开发,虽然读书那会选了网络专业也勉强算是科班出身(哈哈哈哈我真的不太想提及这些往事,因为那个时候自己也还挺不懂事,老在挂科边缘徘徊,经常这边老师教完知识,过段时间我又还给老师了,反正就是成绩不怎么好的那一类人),相对于做开发或者运维等更重技术的岗位,我那个时候更喜欢打游戏、混各种论坛社区写一些挂机脚本、喜欢看人家怎么做产品设计等东西,所以毕业后跑去游戏行业做了产品运营(我在 UC 的那两年就是做产品运营)。

一开始倒是觉得做运营挺有趣的,但后来发现我的运营 KPI 很大程度依赖于在重要节点的各种活动和宣传合作页面用于推广,但 “拿不到排期” 这个事情总是在阻碍着我达成 KPI ,后来我就跟技术部的同事发起了一个小小的请求:“一些简单的需求页面我自己写,你们有空的时候能不能稍微帮我审查下代码和安排部署?” ,然后我就开始在工作时间干起了写代码的活,我负责的运营业务的数据指标也因为需求能如期上线而基本能按预期跑满,从此就一发不可收拾。

但那段时间我还不敢把自己定位为程序员去换工作,毕竟学的越多越觉得自己渺小,计算机的世界里有太多自己没有接触到的东西,所以 2015 年从 UC 跳槽去网易游戏的时候,我还是去面的运营岗位,进入了大话手游业务线,那段时间刚好面临大话手游即将上线,已经在计划中的需求 1234 是真的多,但又是喜闻乐见的 “没有排期了” ,所以入职后作为一个运营,安排给我的第一个任务是写一个答题器,我就……(此处是 Max 的问号脸.jpg )

Max 的问号脸.jpg

其实是我当时的运营简历也写了我会写页面,在网易的面试过程中也聊到了我在 UC 自己做自己的需求的经历,所以当时的网易总监也知道我可以写…… 经过 “一直需求顺利上线一直爽” 的阶段后,再后来就在部门内就成立了自己的技术组、产品组,开始有了自己的前端开发和产品策划等不同岗位,所以从 2016 年开始我就开始正式以写代码为生了,到 2023 年的今年,刚好 7 年。

兜兜转转从技术专业跑去做产品运营,再从运营又杀回来做技术,经常有人问我那几年后不后悔,也有人很好奇我转回技术这段过程难不难。

先说关于难不难,我只想说两个字:“热爱” !当你对一件事情有了足够的热爱,真的没有什么难的。这里的 “热爱” 指的是可以长期保持数年的喜欢不变心,而不是三分钟热度的 “教练我要学” 盲目跟风。

再说说关于后不后悔这件事,至少目前的 7 年里是没有后悔的,我本身从小就不是一个按部就班的人,总是跟着我的兴趣去做我喜欢的事情,青少年时期我的同学都喜欢看球、喜欢听流行歌曲、喜欢唱情歌,而我那个时候就已经是一个享受着孤独沉迷在自己世界里的听摇滚乐的人,从来不看球,从来不听情歌,至于在其他人眼里可能显得比较独来独往和特立独行,从小好像就不是特别在意。

我家境很一般,在我工作之前,我爸爸妈妈每天忙碌养家也只是赚个温饱,所以我基本上是没有零用钱这种东西,虽然早在初中高中那会就想玩乐队,但吉他贝斯等乐器和学琴的费用在当时真的是完全消费不起,但也没有妨碍我对它们一直保持热爱,买不起我就自己做,自己画版型,自己锯木料,自己上油漆,给自己做了最喜欢的 BEYOND 乐队吉他手阿 Paul 在 1991 年演唱会上的那把斯坦伯格的无头吉他模型满足自己内心的 Rock'N Roll ,这可能是我最早期的自己的需求自己实现吧啊哈哈哈。

自己做的吉他模型

工作后身边的同龄人开始恋爱结婚生子学车考驾照,我是完全反着来,因为不喜欢坐车和极少出门,所以我到现在也没有想考驾照的念头,反而在爸妈眼里的 “你也老大不小了(后半句就是你该成家了…… )” 的年纪才开始自学弹琴、组乐队玩演出圆我当初的乐手梦,当年第一把琴还是借的,只想着过把瘾,结果发现真的放不下了,到了后面自己也走上了买自己喜欢的琴的不归路。

左边是借的第一把贝斯,右边是自己的琴

再后来有了一些志同道合的伙伴一起夹 Band ,有幸也登上了几次三千多人的舞台,在别人都是唱流行歌曲和谐氛围里,我们又玩起了相对小众的新金属,哪怕现在我同学孩子都已经很大了,我还每天沉迷在 “塞狗、发克鹰、馊兽” 这样的嘶吼音乐里(这是 Slipknot 活结乐队在 Psychosocial 这首歌的某次现场版开场,特别燥的一个 Live 版)……

对于爱好和生活之间的平衡,这一点我特别佩服中山大学的何广平教授,白天衬衫教书,晚上甩头演出,双面人生太让我羡慕了!(可戳: 一边是量子物理,一边是极端金属,中大教授诠释科研与音乐的完美融合 了解何教授)。

用了好久 “胸口碎大石” 这个乐队名

头发也是从 2018 年初就再也没有去剪过,从光头到莫西干再到现在的长发及腰,我感觉我还可以继续留下去……

“程小姐” 你好…

最让我走上 “只过自己喜欢的生活” 的不归路就是下定决心去文身了,从不到 20 岁的时候就想要纹一条花臂,也是念念不忘了多年后终于开始了行动,第一个文身是我的琴,然后是我的猫,然后是音乐元素,还有一些逆境阶段的有意义的图,有一些是自己设计点草图,到了后面默契上来了就全部交给我的专属文身师 Johnny 了,从 2016 年文身到现在,还在继续加图案,每一个图案都在自己的人生阶段里有着不同的意义。

我的花臂

说了这些往事,主要还是想回到前面说的,只要保持足够的 “热爱” ,很多困难真的不是困难,而且因为热爱,也会自然而然的遇到一群志同道合的好朋友,很感谢这么多年一路陪我玩陪我成长以及在我转行过程中给到了很多帮助的同事好友!

如果对我的更多往事感兴趣,可以扫码查看我之前整理的我在网易五年的工作和生活的记录。

记录一下在网易五年来的工作与生活

表达能力如何培养?

最后分享一个可能会比较多程序员都比较关心的问题,如何锻炼自己的码字能力和表达能力。虽然我很宅,不爱社交,但很多读者在阅读了我的文字之后给我的反馈都是看完容易理解,性格和表达似乎出现了矛盾?其实不然。

我之前写过一篇文章叫 Markdown 工程师的一周 ,分享过我曾经一周写了大约 25 个小时的 Markdown 文档,几乎一天有 5 个小时在码字。

一周写了 24 多个小时的文档

为什么我不觉得写文档是个很费劲的事情?因为我自己常常保持的习惯有主要两个:

一个是保持每天写日记的习惯,把自己的喜怒哀乐都记录到日记里,开心的时候记录起来以后可以回顾,不开心的时候用文字把负面情绪输出走,这个方式可以很好的让自己不会把坏情绪带回家里或者带入到工作中,从几个字到两三千字我都写过,内容的多和少并不需要很刻意的去要求,重要的是这一天对你来说,过的有没有意义,我从 2019 年把写日记的习惯重新培养起来到现在也有 4 年了,已经连续写了 4 年日记,没有中断过。

另外一个习惯是上面说的费曼学习法,把自己学到的东西输出成博客文章或者其他什么载体都可以(例如下面的两个 PPT ,是我之前在一些分享会上的演讲稿,已脱敏),一开始写分享内容时可能会觉得无从下手,但是如果能够长期保持习惯,久而久之自己所写的东西就会开始有了逻辑有了条理。

因为上面两个习惯,让我后来开始记录我平时做菜时的菜谱也蛮多人爱看。

左边是我的小红书账号,右边是我曾经写了两千多字的日记

写在最后

能看到这里的朋友对我真的是真爱了,希望我的书也能给到您一些帮助,希望您在继续学习的路上,或者转行的路上,都可以实现自己想达到的目标!

再次附上买书链接:

☞ 访问 京东商城 购买

☞ 访问 天猫商城 购买

如果想问我接下来会做什么?我也是继续保持学习哈哈哈哈哈哈!我在今年三月份刚刚换了新工作,来到了一个很牛逼的团队,技术氛围特别好,队友们的能力也好强,未来也有很多我之前没有接触的东西,我也还在继续成长!

之前跟朋友说的哈哈哈

最后的最后,分享一下我保持学习的动力和精力的秘诀……

一年多前觉得是这个原因

现在还是觉得是这个原因

谢谢您的支持!

年终总结:2022 年的一些回顾和 2023 年的一些小规划

2022 对我算是比较特殊的一年,虽然因为疫情原因彻底宅了一年,但也没有闲着,换了工作换了城市,回到了阔别两年的第二故乡广州,工作之外的学习和生活对个人的成长也有一些值得复盘的地方。

工作与生活

2020 年疫情爆发那年发生了很多事情,最终也决定从服务了五年的网易公司离职(见 记录一下在网易五年来的工作与生活),随即和几个朋友跟着大佬去了深圳,因为大家都是扎根广州,所以都把深圳当成一个临时过渡的城市,所以今年时机成熟的时候就又一起回到了广州。

回广州后也终于不用再苟在城中村里了,在依山傍水的山景房里,三只猫也迎来了久违的阳光,每天睡到自然醒,醒了有东西吃有玩具玩,还不用出门工作,过的比我还幸福。

我从五月底回来后就一直很忙,因为广州这边也是刚成立的新公司,太多事情要忙,加上业余时间也有其他事情, 菜谱 也慢慢停更了,不过偶尔还是有在做饭,新的一年看看调整下生活节奏重新运营起来。

虽然工作很忙碌,但在初创公司的日子里倒也是很有趣,因为人少(目前加上老板还不到十五个人),所以自己也有机会开始承担起一些前端工程师之外的开发工作,例如写 App ,写客户端,写服务端,写爬虫,写区块链,当然都是围绕着 Node.js 进行的全栈开发。

写着写着就没有什么时间写页面了,所以今年也是把 Tailwind CSS 这一类原子框架大幅度的往工作项目里用,这样在需要写页面的时候可以不怎么写样式了,对 Flex 布局熟悉的前端工程师来说, Tailwind 风格的开发模式做东西真的非常快,很适合初创公司老板说 “啊啊啊这个东西很着急” 然后第二天就可以给到他。

这一年是真的忙碌(见下文的 开源社区 部分),以至于几乎没有什么运动量,可能也因为运动太少,所以从九月份开始,从痛风发作到新冠病毒,陆陆续续当了一个季度的病号,不过有一点算是不幸中的万幸,就是因为一直宅着,所以年底新冠阳性的时候仅仅只是个弱阳,低烧了一个晚上就开始在恢复健康了,相对于在朋友圈看到一些朋友病得五颜六色,我还是挺幸运的。

另外关于生活的话,就是头发达到了历史上的最新长度,往后甩的时候已经过了腰了,虽然发质一如既往的差,但我并不关心发质哈哈哈哈,长度才是王道。

头发达到了新的长度

开源社区

今年在 GitHub 的活跃度比去年高了不少,而且很神奇的达到了全勤,因为疫情的原因,还有换城市和新工作等事情,今年也没有出远门或者回家待几天,每天都在有电脑的屋子里待着,所以每天都有在 GitHub 上操作(截图生成自 GitHub Contributions ),也归功于今年把很多有的没的代码都托管到 GitHub 上了,包括一些 to-do list 也会有个 Markdown 仓库作为临时笔记,很方便哈哈哈。

这两年在 GitHub 的活跃情况

虽然这么活跃,但开源项目其实没有参与多少,基本上都在维护去年的一些旧项目,其中投入最多的是之前写的那本关于 Vue 3 的书 Learning Vue3

今年一月份的时候在知乎收到一条私信,是出版社的编辑发现了我写的东西,邀请我参与到书籍的出版工作中。当时由于临近过年比较忙,没有立即答复是否参加,后面四月份编辑老师又联系了我,在考虑了自己的时间和目前手里头比较有积累有篇幅的内容后,咨询了是否可以用现有的开源内容整理出版(还举了阮一峰老师的 ES6 入门教程 的例子),得到了肯定的答复之后,就开始安排时间调整 Learning Vue3 的内容。

由于当时 Vue 3 已经成为 npm 安装时的默认版本,并且中文官网也建设起来了,考虑到继续单独讲述 Vue 3 的内容显得有点 “过时” (毕竟当时写这本书的背景是国内没有什么 Vue 3 的资料,并且还处于公测但未完全替代 Vue 2 的阶段),在出版社编辑老师的建议下,续写了关于前端工程化的内容,以降低本书的学习门槛,所以可以看到我从 四月底的更新记录 就慢慢不再局限于 Vue 了,而是一直在更新前端工程化的内容。

再之后的几个月就一直在补充新内容,并且按照出版社的供稿规范调整了叙述风格,最终整个文档的风格就变得比较有 “专业性” ,不再是嘻嘻哈哈的逗逼语气,自己感觉有点像阮一峰老师,确实在阮老师的博客里受到了很大影响。

很高兴续写的内容得到了线上读者的认可,不仅仓库从 100 Star 慢慢涨到了现在接近 500 Star ,还有很多读者给我留言,给了我很大的鼓励!

仓库接近 500 Star 了

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

调整后的叙述风格也被读者认可,在 2022 年底突然有一群台湾开发者关注了我的 GitHub 账号,搜索了一下才发现原来 被台湾一位技术大 V 推荐了 ,她很喜欢我的风格,我很开心哈哈哈,付出得到了回报,得到了认可!

台湾网友的分享

其他的开源项目大部分是一些 npm 包,总下载量也突破 35 万次了(数据来源 npm-stat )。

来自 npm-stat 网站的统计数据

有个 Vite 插件被大佬的项目引入后带火了,现在每周大约保持在 7k 左右的下载量,也收到了其他开发者的 PR 贡献( #16 ),期间认真的评审了代码和给予了意见点评,直到那天才感觉在认真参与开源项目协作,在此之前都是自己单打独斗,或者发起一些没有什么修改意见的翻译 PR ,很少这样在 PR 过程中讨论功能的取舍。

在 PR 过程中讨论功能的取舍

还有一个使用量比较多的插件是一年多前做的一个自用的小工具,很久没维护,结果一直有人用,前段时间还提了一些反馈,所以年底有空于是重新维护了起来,还用 Photoshop 鼠绘了个 Logo ,偶尔设计点东西还挺有意思的,虽然渣渣,但我觉得很贴合用途哈哈哈。

新官网上挂着自己画的 Logo

另外还有一点比较值得高兴的是,新开坑的开源仓库都已经全部使用英语了,包括代码里的注释,还有 issue 的交流,虽然语法和用词什么的不一定对,但在 Google 翻译校验了一下还是可以识别的,多读多写多练习,总有一天可以不太依赖翻译工具的。

新年展望

刚好很多事情都在 2022 年底弄完了,新的一年业余时间应该不会那么忙了,我要多运动,多爬山,明明现在住的地方对面就是一座山,我居然一次都没有爬过,真是浪费啊浪费可耻哈哈哈。

底迪很喜欢在阳台看山景

然后把三年没弹的贝斯重新弹起来,太久没有弹琴了,自从去了深圳,总感觉人在漂泊,以至于从 2020 年之后就没有再弹过琴,希望今年可以重新积累一些可以演奏的曲子。

技术方面,长远的规划不好说,梳理了一下最近几个月的计划吧:

  1. 重构博客(上一次是 2020 年 1 月,也有两年了),技术选型还是偏向于比较新的前端技术栈,找个时间调研一下
  2. 认真学一下 React ,其实很久前就有想法,但因为缺乏可落地的个人项目,所以一直没动手,只是简单的写了几个 demo ,去年写 App 的时候因为主要技术栈还是 Vue ,所以选了 uni-app ,但感觉体验并不是太好,把 React 玩熟悉之后以后也可以切入 React Native 开发
  3. 重构之前写的命令行工具 create-preset ,一个模板拉取工具,现在在用的 GitHub 镜像挂掉了一直没去修,打算把一些自己维护的模板处理成本地一起发包,不走 GitHub 下载了,其他人的社区模板就继续拉仓库,不过要考虑现有的 API 兼容,只能是无感知的重构,感觉要做的事情也不少

其他的可能就是 Web 3 方面的开发继续深入吧,智能合约和 dApp ,也是未来的趋势,并且基本上都是以前端的技术栈为主,作为前端工程师,天生有前端优势的情况下,不玩下区块链多可惜!

用Vite更简单的解决Vue3项目的预渲染问题

之前 Webpack 项目经常会用到预渲染,现在团队都开始用 Vite 了,所以弄一个基于 Vite 的 Vue 3 预渲染 demo 可以参考。

预渲染和静态生成器比较接近,也可以参考我的 SSG 博客vite-ssgvite-plugin-pages 来处理。

不过 Vite 本身对预渲染也提供了原生的支持,简单的预渲染可以自己写写代码来改造实现。

HTML 部分

项目根目录下 index.html 里需要追加至少两条资源注入位置的注释:

注释语句 作用 是否必须
<!--preload-links--> 预加载资源
<!--app-html--> 页面内容
<!--title--> SEO 优化:写入标题
<!--description--> SEO 优化:写入描述
<!--keywords--> SEO 优化:写入关键词

并把入口文件改成 entry-client.ts ,原来的 main.ts 会作为客户端和服务端启动时的引用。

完整代码如下(源码:index.html ):

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!--title-->
    <!--description-->
    <!--keywords-->
    <!--preload-links-->
  </head>
  <body>
    <div id="app"><!--app-html--></div>
    <script type="module" src="/src/entry-client.ts"></script>
  </body>
</html>

其中除了两条必须的注释语句外,可选的部分见 SEO 优化

入口文件

普通项目是使用 src/main.ts 作为入口文件,需要改造成两个入口:

注释语句 作用 源码
entry-client.ts 客户端入口 查看源码
entry-server.ts 服务端入口 查看源码

而原来的 main.ts 只作为入口函数导出,详见源码: main.ts

路由

不再需要手动配置路由结构了,改造后直接读取 src/views 的路由组件来生成页面路由。

详见源码: router

SEO 优化

做预渲染为的就是做 SEO ,所以需要自己提前配置好 SEO 的 TKD 三大要素,这里我也是放在 src/router 目录下一起管理了。

实现逻辑见 预渲染 部分的说明,这里是以最终每个页面的相对路径来判断要写入的 TKD 信息的。

export default [
  {
    url: '/',
    title: '首页',
    description: '这是首页',
    keywords: ['关键词1', '关键词2'],
  },
  // ...
]

详见源码: seo

预渲染

scripts/prerender.ts 这个文件是执行预渲染行为,可以按照路由目录的结构渲染为静态 HTML 文件。

运行 npm run generate ,可以把 dist/static 作为静态站点部署。

当然我也封装了 npm run build 一次性编译所有平台( Client / Server / Static )。

详见: package.json 里的 scripts 部分。

常见问题

改造过程中遇到的几个问题:

水合节点不匹配

控制台报错:

Hydration node mismatch:
- Client vnode: div
- Server rendered DOM: <!--app-html-->

警告来自于 hydration.ts ,一般可以无视……

当然也可以了解更多的知识点: understand-and-solve-hydration-errors-in-vue-js

路由跳转

控制台报错:

Unhandled error during execution of scheduler flush

需要使用 <Suspense /> 标签来包裹路由视图,详见 Suspense

<template>
-  <!-- <router-view :key="key" /> -->
+  <router-view :key="key" v-slot="{ Component }">
+    <Suspense>
+      <div>
+        <component :is="Component" />
+      </div>
+    </Suspense>
+  </router-view>
</template>

Pinia怎么用?Vue3全局状态的管理工具Pinia教程

Vue 官方推出的全局状态管理工具目前有 Vuex 和 Pinia ,两者的作用和用法都比较相似,但 Pinia 的设计更贴近 Vue 3 组合式 API 的用法。

教程地址

点击访问:全局状态的管理 查看完整内容。

为什么写这篇内容

截止至 2022 年 4 月, Pinia 还没有被广泛的默认集成在各种脚手架里,官网也只有英文版,整理了教程便于提前学习。

由于 Vuex 4.x 版本只是个过渡版,Vuex 4 对 TypeScript 和 Composition API 都不是很友好,虽然官方团队在 GitHub 已有讨论 Vuex 5 的开发提案,但从 2022-02-07 在 Vue 3 被设置为默认版本开始, Pinia 已正式被官方推荐作为全局状态管理的工具。

Pinia Logo

Pinia 支持 Vue 3 和 Vue 2 ,对 TypeScript 也有很完好的支持,延续了 Vue3 入门指南与实战案例 的宗旨,在这里只介绍基于 Vue 3 和 TypeScript 的用法。

欢迎 Star

教程属于 Vue3 入门指南与实战案例 的一部分,如果觉得对你有帮助,欢迎到仓库给个 Star 鼓励

从前端开发者身份入门Flutter和Dart的学习笔记

最近有空,接触一下 Flutter 和 Dart 的开发,虽然说前端入门 Flutter 比较友好,但个人觉得最最最开始其实不怎么友好,当然写了几个小时 Dart 之后感觉确实都是熟悉的身影,但真的刚入门的那一两个小时真的特别困,所以还是要不定期记录一些遇到的问题。

本文主要面向平时写 Vue + TypeScript 的开发者,主要通过一些常用知识点的对比,来加快对 Flutter 的入门学习,因为很多道理是相同的,区别只在于怎么用。

在线文档

国内很多中文文档都十分陈旧,主要原因跟 Flutter 的版本更新比较频繁有关系,如果看国内那些老文档,你连 Hello World 都跑不起来…… 建议直接阅读官方的英文文档。

点击阅读:Flutter 官方英文文档

不过也有本不错的开源书一直在更新维护,也值得一看!

点击阅读:Flutter 实战·第二版

Dart 语言上手倒是没有太大的难度,主要了解一下语法结构就好,大部分在敲 DEMO 代码的实践过程中就能理解和记住了,在 Flutter 实战这本书有一章是总结了一些 Dart 的速记,可以看看。

点击阅读:Dart 语言简介

另外对于编程风格,建议有空先看一波 Flutter 官方提供的风格指南。

点击阅读:Style Guide For Flutter Repo

项目起步

建议先阅读一遍 万字长文轻松彻底入门 Flutter,秒变大前端,有个大概的了解再自己折腾。

看完了,就开始折腾了,作为一个前端,相对于 Android Studio ,当然是更愿意用 VSCode 啦!

用 VSCode 的好处是自动热重载, 用 Terminal 需要自己手动在命令行敲 r 才能刷新,就很 emmm……

VSCode 支持

VSCode 的 Flutter 和 Dart 支持特别友好,安装对应的插件就行。

启动调试

调试有两种方式,一个是直接在 Chrome 上预览网页版,一个是在模拟器上预览(我是 Windows 开发所以也只能体验一下安卓模拟器)。

Chrome 模拟器

调试的话可以通过 VSCode 的 lunch.json 配置一个 Flutter 启动。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Flutter",
      "type": "dart",
      "request": "launch"
    }
  ]
}

或者在 Windows Terminal 等终端里 cd 进项目目录,跑 flutter run 运行。

会唤起 Chrome 浏览器,支持热重载。

安卓模拟器

我按照这个教程配置了一波,运行成功了。

[Flutter] 调用 VS Code 模拟器(虚拟机), 不借助 Genymotion

项目入口

lib 文件夹是 main.dart 为应用程序的入口文件, runApp 是 Flutter 应用的入口。

void main() {
  runApp(const MyApp());
}

其他的文件可以参照平时的开发习惯,分文件编写后 import 进来。

阿里有个 Star 很多的项目 Flutter GO,可以参考下项目结构

路由管理

Flutter 和 Vue 一样,也是有路由的概念,通过定义路由文件和关联路由表,即可实现一套 APP 的路由。

一般情况下(我个人觉得的)最好是用命名路由并通过路由名称跳转。

MaterialApp(
  // ...
  // 在这里注册命名路由
  routes: {
    'list': (context) => const ListPage(),
  },
  home: const MyHomePage(
    title: '程沛权',
    avatar: 'https://avatars.githubusercontent.com/u/24845958?v=4',
  ),
);

比如,如果单纯的从首页 push 到列表页面,地址栏不会更新,依旧是首页的地址,热重载刷新后依然是回到首页去,开发调试过程中非常麻烦。

// 地址栏依旧是 http://localhost:55368/#/
Navigator.push(context, MaterialPageRoute(
  builder: (context) {
    return const ListPage();
  },
));

但是如果通过 pushNamed 来跳转到注册的路由,则可以得到一条专用的地址(地址格式和 Vue Router 的 hash 模式一毛一样……),这样热重载后依然可以停留在列表页。

// 地址栏会切换到 http://localhost:55368/#list
Navigator.pushNamed(context, 'list');

点击阅读:路由管理 这一章。

设计风格对比

在 Flutter 有两类主流的设计风格: Material 和 Cupertino 。

Material widgets 实现了 iOS,Android 和 web 三端的 Material 设计风格。

Cupertino widgets 基于 Apple Human Interface Guidelines 实现了当前的 iOS 设计风格。

一般情况下都是默认采用 Android 的 Material 风格, DEMO 也是这样,不知道实际开发的时候会不会用 Cupertino 还是其他,有待实践。

有一篇文章做了一些选择方面的介绍: Material 還是 Cupertino?

部件速记对比

都说写前端的人比较容易上手 Dart 和 Flutter ,感觉虽然确实没有那么难入门,不过一开始也会觉得一头雾水,这里放一些标签对比,在实现功能的时候可以快速找到应该用什么部件:

HTML Tag Flutter Widget
div Container
span Text
img Image

更多的部件可以查看官网的 API 文档:API Docs - Flutter

还有两个比较重要的部件类型需要记住:

类型 用途说明
StatelessWidget 无状态变更,UI 静态固化的 Widget, 页面渲染性能更高。
StatefulWidget 因状态变更可以导致 UI 变更的的 Widget,涉及到数据渲染场景,都使用 StatefulWidget。

运算符

虽然 Dart 的运算符和 TypeScript / JavaScript 大同小异,但还是有些不太一样,比如……

The '===' operator is not supported.dart(unsupported_operator)

建议看一遍:Dart 运算符、流程控制

常见问题

建议先阅读一波 Flutter 实战 那本书,起步流程什么的,在上面都有,这里只记录一些踩坑到比较蛋疼的问题。

Android SDK 报错

Flutter 开发需要有 Android Studio 环境,但是安装了 Android SDK 后会报 unable to access android sdk add-on list ,在 StackOverflow 找到了解决方案:

  1. 打开 Settings
  2. 点击 HTTP Proxy ,选择 Auto-detect proxy settings
  3. 测试 youtube.com

参考链接 Unable to access Android SDK add-on list

安卓模拟器报错

暂时无解,折腾了 2 个上午都没搞明白怎么弄,可能公司电脑的问题,等回家再试试。

报错界面

GlobalKey 报错

在调整路由结构的时候,遇到一个 GlobalKey 的报错如下:

A GlobalKey was used multiple times inside one widge's child list…

一开始是把所有路由都挂到了命令路由里去了,包括 home ,所以首页就崩溃了。

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(primarySwatch: Colors.blueGrey),
  darkTheme: ThemeData(primarySwatch: black),
  routes: routes,
);

然后把首页配置回来才可以,看来首页还是要独立抽离一个配置。

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(primarySwatch: Colors.blueGrey),
  darkTheme: ThemeData(primarySwatch: black),
  routes: routes,
+  home: const HomePage(
+    title: '程沛权',
+    avatar: 'https://avatars.githubusercontent.com/u/24845958?v=4',
+  ),
);

打印 LOG

类似于 console.log , Dart 使用 print 来打印 LOG ,如果是在 Chrome 模拟器预览的话,按 F12 打开 Console 面板就可以看到 LOG 了。

不过有个问题就是 VSCode 一直报一个很烦人的提示,查了一下原来是新版本的 Flutter 要求用 debugPrint 代替 print 了。

点击查看:Avoid print calls in production code. (Documentation)

自定义 Widget

在 Flutter , widget 类似于前端项目的 component ,在实现的时候目前探索是有两种方式可以写出来:

一种是前端常用的函数式编程,通过函数直接 return 一个 widget

Widget Avatar(
    {required String url, required double width}) {
  return Container(
    // ...
  );
}

一种是面向对象编程,通过 class 去定义一个 widget

class Avatar extends StatelessWidget {
  const Avatar(
      {Key? key, required this.url, required this.width})
      : super(key: key);

  final String url;
  final double width;

  @override
  Widget build(BuildContext context) {
    return Container(
      // ...
    );
  }
}

在 StackOverflow 上有关于这个问题的讨论:What is the difference between functions and classes to create reusable widgets?

还有一个官方在 YouTube 上的视频教程说明这一点: Flutter 解析:小部件与辅助方法(Helper Method)

大佬总结了一些对比就是:

Classes 的优点

允许性能优化( const 构造函数,更细粒度的重建) 确保在两个不同布局之间切换正确处理资源(函数可能会重用某些先前的状态) 确保热重载正常工作(使用函数可能会破坏 showDialogs 之类的热重载) 已集成到小部件检查器中。 我们 ClassWidget 在 devtool 显示的小部件树中看到,这有助于理解屏幕上的内容 我们可以重写 debugFillProperties 以打印传递给小部件的参数是什么 更好的错误消息 如果发生异常(如 ProviderNotFound ),框架将为您提供当前构建的小部件的名称。如果您仅在函数 + 中拆分小部件树 Builder ,则您的错误将没有有用的名称 可以定义 Keys 可以使用上下文 API

Functions 的优点

代码更少(可以使用代码生成功能小部件来解决)

所以记住最好是通过类去定义一个 widget ,而不是函数。

无效的 Material Color

这个情况是出现在准备给 darkTheme 设置黑色的 AppBar ,选择了 Colors.black 结果报了这个错误。

The argument type 'Color' can't be assigned to the parameter type 'MaterialColor?'.dartargument_type_not_assignable

原因是部分颜色不属于 MaterialColor 类型,如果要使用,需要自己创建:

const MaterialColor black = MaterialColor(
  0xFF000000,
  <int, Color>{
    50: Color(0xFF000000),
    100: Color(0xFF000000),
    200: Color(0xFF000000),
    300: Color(0xFF000000),
    400: Color(0xFF000000),
    500: Color(0xFF000000),
    600: Color(0xFF000000),
    700: Color(0xFF000000),
    800: Color(0xFF000000),
    900: Color(0xFF000000),
  },
);

点击查看:Flutter 更改主题颜色报错

‘Color‘ is not a subtype of type ‘MaterialColor‘

缺少参数传入

如果你定义了一个部件:

class Foo extends StatelessWidget {
  const Foo(Key? key) : super(key: key);
  // ...
}

但是调用 Foo() 的时候会报这样的错误:

1 positional argument(s) expected, but 0 found.

我明明不需要传 key 啊!!!为什么???

Dart 和 TypeScript 不一样的地方在于, TS 通过 ? 直接可以定义参数可选,但是 Dart 的可选参数需要用花括号 {} 括起来,在类型后面加上问号 ? 仅仅只是用来声明为可空,所以这样定义实际上还是表示必须传 key

所以要改成这样(注意 Foo 后面的 () 变成了 ({}) ):

class Foo extends StatelessWidget {
-  const Foo(Key? key) : super(key: key);
+  const Foo({Key? key}) : super(key: key);
  // ...
}

图片部件

Flutter 的图片部件效果和 HTML 的 img 整体还是比较相似,不过用法上有些差距,但很好记,稍微写一下就能记住了。

写法的区别

一开始比较迷惑 Image.networkNetworkImage 有啥区别,看了一下还真是有区别……

Difference in NetworkImage and Image.network?

适应效果

和 CSS 的 object-fit 一样, Flutter 的图片也支持配置 fit 效果,支持的样式效果和 CSS 大同小异。

Image.network(
  'https://example.com/cover.jpg?x-oss-process=image/interlace,1',
  fit: BoxFit.cover,
)

关于图片的更多说明可以戳:图片及 ICON

设置圆角

这个一定要点名!笑死,一个设置圆角的问题被浏览了 20 多万次……

209k 的浏览量…

具体戳:Add border to a Container with borderRadius in Flutter

静态资源

Flutter 也有类似于 Vue 的 public 文件夹,存放一些静态资源,但是这里的路径有个坑,反复查了很多遍 Adding assets and images 文档都没有说引入的时候文件是要放在哪里,从哪里引入( Vue 就有明确的说明 public 下的资源是从根目录读取),所以花了很多时间在调试路径的问题。

不过还好机智的打印了一波路径和看请求,最终还是跑通了。

首先需要去项目根目录下的 pubspec.yaml 文件里配置 assets 字段的数据:

flutter:
  # To add assets to your application, add an assets section, like this:
  assets:
    - assets/mock/

这里的路径注意了! assets/mock/ 代表着我把 mock 文件夹放在了项目根目录下的 assets 文件夹里,为什么不是别的地方?

因为经过不断打印错误的路径,发现 Flutter 的静态资源真的是从 /assets/ 开始的,不是根目录,也不是基于当前文件去写相对地址。

注释里的 like this 真的就是要跟他一样从 assets 开始配置,而不是 YAML 语法格式相同就好……也就是我这里的 JSON 文件的路径是类似:

http://localhost:52128/assets/mock/list.json

这样在 Dart 文件里就可以直接省略掉 assets 开头:

await DefaultAssetBundle.of(context).loadString('mock/list.json');

否则放别的地方你还要一直 ../ 之类的去写更复杂的相对路径。

解析 JSON

因为要 Mock 一些数据,所以写了几个 JSON 文件作为静态资产去导入,有几个需要注意的:

// 确保导入了这个库
import 'dart:convert';

然后才可以使用 jsonDecode() 或者 json.decode() 去解析 JSON 内容。

参考资料:How to decode JSON in Flutter?

构建异步部件

APP 肯定离不开网络请求,包括 Mock 的 JSON 数据,都是请求回来的,在这里折腾了很长时间才解决,倒不是文档看不懂,而是因为 VSCode 的 Dart 代码补全和类型补全帮我搞了几个麻烦的问题…… 所以在没有绝对把握之前,还是先看文档,再去各种补全代码才是王道。

直接贴上相关的文章吧。

点击阅读:Dart 的异步支持

点击阅读:Flutter 异步 UI 更新

点击阅读:How to Build Widgets with an Async Method Call

Markdown工程师的一周

马上就快过年了,最近才比较有时间把之前很多想搞一直没时间搞的东西弄一下,比如说最近一周在干的事情就是,补各种文档。

最近一周的常用语言

我是没想到我一周能敲差不多 25 个小时的 Markdown … 几乎一天有 5 个小时在码字,天了噜…

都干了些啥

  1. 完善了公司的内部 Wiki ,之前很多东西想沉淀,结果一直忙,码字也挺费时间的说

  2. 完善了一些内部项目的主 README 和 子 README ,这部分占比很小,因为一般在开坑之前就会把它们都写好,最近只是根据项目的近况做了一些修改

  3. 写了一个 73 页的项目复盘 PPT ,哈哈哈说是 PPT ,其实也是在敲 Markdown ,这部分占比比较大 (可以戳下面的 关于复盘 部分)

  4. 元旦开了个开源的坑,申请了个 js.org 的免费域名,把官网文档给做了,用的是 VitePress ,所以码字时间也挺多

  5. 在年底的时候,博客终于把 2021 年立的 Flag 给摘了,菜谱专栏 上线了,在陆陆续续抽时间填我之前写的菜谱,所以写菜谱的时间也占了不少

大概就这些吧,我每天还有写日记的习惯,不过日记用的是手机 APP ,所以就不包含在 Markdown 里了,下面挑几个聊一聊吧!

关于菜谱

2021 年在重构自己博客的时候更新了一下 About ,里面提到了我打算把自己做的菜都整理一下做法,保守应该有 600 多道不一样的菜式吧!

自己做的菜

不过因为工作忙,一直拖着,终于拖到了年底搞出来了,目前还在填内容,有兴趣的话可以先收藏起来,菜品方面大多是广东甚至于潮汕地区为主,毕竟我是广东潮汕人。

点击直达:菜谱专栏

目前已发出来的菜谱

博客的专栏填充会比较慢,不过我从 8 月份开始就在小红书上更新菜谱笔记了,一般也会先发在上面,你也可以关注我的小红书号来获取最新的做菜信息,有啥不清楚的也可以直接私信问我。

欢迎关注

关于开源官网

基于自己很长时间以来的一个需求点吧,就是常用的一些项目模板或者配置,缺少一个比较好用的管理工具。

虽然有 Git 仓库,但多了也不好找,所以元旦有空就开了个坑,虽然还在搞,不过基本的一些功能还是可以用的,特别是本地的配置管理。

# 都是国内我就用 CNPM 吧,先全局安装
cnpm i -g create-preset

# 然后初始化就可以创建一套 Starter
preset init

点击访问:项目文档 了解更多用法。

create-preset

目前还处于初期自己摸索阶段,很多功能还在想怎么完善,如果有这方面的需要可以先用本地管理去体验。

点击查看:管理本地配置

有 BUG 就先提 issue 吧,因为今天才用 TypeScript 重写了一版,还没怎么测试。

原本是用 JavaScript 直接写的,不过还是决定用 Vite 重新构建为 Library 的形式,至于重写的原因可以看 使用感受

关于项目复盘

这部分是我觉得这周写的最有意义的一个东西了,虽然从 2021 年出就一直在团队里逐步升级用 Vue 3.0 和 TypeScript ,不过很长时间只有我亲自开坑的项目才会有这种待遇,团队其他人开的坑都还是比较保守,一方面是不熟悉,一方面可能是懒的学?

最近带着几个小兄弟把一个以前的老项目重写了,从 Vue 2.0 + JavaScript + Webpack 更换到了 Vue 3.0 + TypeScript + Vite ,整个开发过程大家都很愉快!

虽然说就是一个后台而已,但我觉得它的意义并不止于一个后台,而是作为一个小跳板或者噱头,让团队的人都比较有亲身体验的去感受这些新技术栈的优点,并且对于一些规范的东西也有更多的感受,所以我花了几天时间写了个复盘 PPT ,在复盘的过程中,大家也都能了解到我的一些想法,不仅仅是因为我说用什么就用什么。

发现复盘之后大家很多东西都有了更多的理解和认可,我感觉很棒!分享不涉及业务的一部分吧,如果你的团队也在纠结要不要用新技术栈,可以参考一下看怎么推进。

Btw: PPT 我用的是 slidev ,一个基于 Vite 和 Vue 3.0 的幻灯片工具,上手很方便,功能很强大!

  1. 这个是复盘的大纲,主要围绕这些维度去对这次项目进行回顾。

这个是复盘的大纲

  1. 这是重构和重写之间的思考,避免有人觉得我只是单纯不喜欢老项目太多外包团队写的代码,所以决定重写,其实不是的哈哈哈哈,中间分析了很多东西,细节部分这里就不贴出来了

思考过程

  1. 决定重写没问题,但是最重要的一点就是,保证如期上线!

话说这里安利一下我的开源书 Vue3.0学习教程与实战案例 ,虽然 Vue 3.0 的新官网快出来了,变化很大,不过在出来之前还是可以看看,这次我们组的同学都是一边干活一边看我这本书,很容易就上手 Vue 3 了。

如何保证也要提前想好

  1. 这是这次更换的几个核心技术栈

变化了这些

  1. 一些技术选型的理由分析

Vue 2 和 3 的对比

Options API 和 Composition API 的对比

JS 和 TS 的对比

TS 的一个流行程度也是作为理由

这是分析 Webpack 和 Vite 的区别

这次是选择了 Vite

UI 框架也做了一次更换

  1. 然后回顾了一些亮点,主要是和旧项目做对比,不复盘的话只会觉得不就是做了个后台吗,复盘了发现其实团队的很多东西变化很大,积累的东西也比之前好了很多(小团队轻喷)

一些亮点

  1. 这次也引入了很多开源社区比较好的规范机制,有 ESLint 啊,Commit 规范啊等等(小团队轻喷,之前确实没有太严格这方面的东西)

大佬们做榜样

大概就这几个吧!其他的都是内部的东西就不多说了哈。

回到本文的主题,做 Markdown 工程师的感觉怎么样?其实还挺不错的哈哈哈哈。

TSC编译时指定生成d.ts的目录 并解决无法导入package.json和alias别名的问题

虽然之前在构建 JS Library 的时候,也是有生成 d.ts 文件在输出目录,但总归比较凌乱,意思就是构建出来的 JS Library 和 DTS 文件都在同一级文件夹里。

如果是单个 DTS 文件还好,但有时候构建出来会有好几个 DTS ,这种情况下都放在一起总觉得有点别扭,所以今天想看一下能不能更优雅一点,存档在 types 文件夹里,或者是合并成一个文件。

开始尝试

根据官网的 tsconfig 说明,很快就搞好了目标配置(这里省略了其他选项,只展示与生成 DTS 相关的部分):

{
  "compilerOptions": {
    "resolveJsonModule": true,
    "noEmit": false,
    "rootDir": ".",
    "outDir": "./dist",
    "emitDeclarationOnly": true,
    "declaration": true,
    "declarationDir": "./dist/types"
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

执行 build 命令,很完美的,生成的 d.ts 文件都会放在我们指定的 dist/types 文件夹下。

不过好像有哪里不对,原来是它还根据源码的目录格式,生成多了一级 src ,变成了 dist/types/src ,强迫症不能忍啊!

多了一级 src 目录

修改 rootDir

再次阅读了 TypeScript 官网的说明,把 rootDir 指向源码目录:

{
  "compilerOptions": {
    "resolveJsonModule": true,
    "noEmit": false,
-    "rootDir": ".",
+    "rootDir": "./src",
    "outDir": "./dist",
    "emitDeclarationOnly": true,
    "declaration": true,
    "declarationDir": "./dist/types"
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

此时编译后,已经可以看到 DTS 文件成功的生成到 dist/types 文件夹下了!不再带有那一层 src 文件夹。

但是!但是程序的正常编译居然报错了!源码里有一部分数据是导入了 package.json 的字段作为值,比如版本号,包名等等,所以这里出现了一个报错。

修改 rootDir 之后报错了

解决方案

既然是 JSON 导入出错,那么问题很明显是跟 JSON 配置有关,尝试关闭 tsconfig 里的 resolveJsonModule 选项:

{
  "compilerOptions": {
-    "resolveJsonModule": true,
+    "resolveJsonModule": false,
    "noEmit": false,
    "rootDir": ".",
    "outDir": "./dist",
    "emitDeclarationOnly": true,
    "declaration": true,
    "declarationDir": "./dist/types"
  },
  "include": ["./src"],
  "exclude": ["node_modules"]
}

然后打开你源码根目录下的 DTS 文件,声明一个 module 用于识别 JSON 文件:

// e.g. src/global.d.ts
declare module '*.json'

再次执行 build ,完美达成预期!

完美达到预期

来到这里第一个小目标是已经达成了!接下来看看怎么合并成单个文件。

合并文件

查了好久资料,确认单纯通过 TSC 编译出来的 DTS 文件无法做到只有单个,如果你确实觉得这种跟源码目录一样的 DTS 文件还是过于累赘,那么需要借助外部插件来实现文件合并了。

e.g. dts-generator

这样构建出来的 DTS 文件只有一个,比如 dist/types/index.d.ts

处理别名

到目前,如果你只是简单的项目结构,都是通过 ../foo/bar 的相对路径来引入的话,没有什么问题。

但如果你跟我一样用了 alias 别名,会发现生成的 DTS 文件并不支持 alias (比如源码里通过 @foo/bar 来代替 src/foo/bar 或者 ../../foo/bar )。

这个也是要借助外部插件来实现转换,这里测试了几款外部工具,最有效的是 tscpaths

需要明确的是,它本身不支持编译生成 DTS 文件,而是在 TSC 编译时,根据 tsconfig.jsonpaths 配置,将 TypeScript 编译出来的 DTS 文件里的绝对路径替换为相对路径。

pnpm add -D tscpaths

然后在你的 package.jsonbuild 命令里面,将它补充在 tsc 命令后面,比如:

{
  "scripts": {
    "build": "tsc && tscpaths -p tsconfig.json -s ./src -o ./dist/types && vite build"
  }
}

这样 DTS 里的路径就不再是 @foo/bar 了,而是根据目录层级自动转换成 ../../foo/bar 这样的相对路径。

参考资料

'package.json' is not under 'rootDir' - StackOverflow

Export single .d.ts from several typescript files + entrypoint - StackOverflow

tsc - doesn't compile alias paths - StackOverflow

git-commit-analytics 分析Git Commit记录生成工作日报

一个可以分析你的 Git 仓库 commit 记录的工具。它可以帮你生成一份工作日报 / 周报,或者你需要的更长时间范围的工作报告。

git-commit-analytics

客户端下载

这是一个客户端工具,所以你需要下载程序去使用它,点击 最新版本 去下载客户端。

更新记录

你可以查看 更新记录 去了解每个版本的更新内容。

使用说明

创建并填写你的配置文件,然后运行程序,即可获得你的工作报告。

配置文件

你需要在与程序相同的文件夹下,创建一个名为 config.json 的文件,并写入以下格式的内容。

{
  "lang": "en",
  "authors": ["chengpeiquan"],
  "dateRange": ["2021-12-01", "2022-01-31"],
  "repos": ["D:\\Git\\git-commit-analytics"],
  "format": {
    "git-commit-analytics": "Git Commit Analytics"
  },
  "includes": ["feat", "fix", "docs", "style", "refactor", "test", "chore"],
  "excludes": ["typo", "backup", "progress"]
}

配置项说明如下:

key type description
lang string 设置软件的默认语言,支持 en (英语)和 zh (简体中文)。
authors string[] 筛选 commit 的作者名称,支持多个作者名称,用于你在不同的仓库可能有不同的名字。
dateRange [string, string] 填写 [开始日期, 结束日期] , 支持合法的时间格式,会从开始日期的 00:00:00 统计到截止日期的 23:59:59
repos string[] 你电脑里的 Git 仓库文件夹,需要提前切换到你要统计的分支。
format { [key: string]: string } 格式化你的文件夹名称为项目名。
includes string[] 要纳入统计的 commit message 前缀。
excludes string[] 在统计出来的结果里,排除掉包含了这些关键词的 commit message 。

Among them, authors / includes / excludes will be created as regular expressions to match data.

其中,authors / includes / excludes 会创建为正则表达式去匹配数据。

工作报告

The report file will be generated in markdown syntax (probably the most common format for developer?) and saved as a file in .txt format (probably the most compatible format?).

报告文件会以 markdown 语法生成(可能是对程序员最通用的格式?),并以 .txt 格式的文件保存(可能是兼容性最好的格式?)。

The project name will be classified as the second-level title, and 7 types of commit prefixes will be classified as the third-level title:

会以项目名称作为二级标题归类,以 7 个类型的 commit 前缀作为三级标题归类:

type description
feat 功能开发
fix BUG修复
docs 完善文档
style 优化样式
refactor 代码重构
test 测试用例
chore 其他优化

你可以点击 Commit message 和 Change log 编写指南 学习如何规范化提交 Git Commit 。

我写了一本书《前端工程化:基于 Vue.js 3.0 的设计与实践》 想分享一下它背后的故事

[[toc]]

大家好,我是程沛权,经过差不多一年时间的打磨和优化,我的第一本技术书籍《前端工程化:基于 Vue.js 3.0 的设计与实践》出版上市啦!

前端工程化:基于 Vue.js 3.0 的设计与实践

这是一本以 Vue.js 的 3.0 版本为核心技术栈,围绕 “前端工程化” 和 TypeScript 的知识点展开讲解的前端入门书籍,主要面向以下读者人群:

  1. 掌握了基础的 HTML 页面编写知识,想学习一个主流前端框架的新手前端工程师
  2. 已经学会了 Vue 2 ,面对 Vue 3 的大版本更新,想快速上手使用的前端工程师
  3. 非职业前端开发,但涉及前端的工作,需要掌握一个主流前端框架的全栈工程师

书里面的知识点是按照工程师做项目的顺序梳理的,比较循序渐进的一个过程,读者可以收获到这些知识:

  1. 了解如何入门前端工程化开发,掌握 Node.js 和 npm 的使用
  2. 掌握前端领域多年来趋势走高、带有类型支持的 TypeScript 语言
  3. 上手主流前端框架 Vue.js 的全新版本,并且在遇到常见问题时知道如何解决

看到这里,我估计有部分读者会觉得眼熟,有熟悉的感觉是对的!因为在它被正式出版之前,有另外一个名字是叫《Vue3 入门指南与实战案例》,最早是部署在我的博客网站上作为本书配套案例和代码资源的 开源版本 分享的。

截止至 2023-05-03 五一假期的最后一天,开源版本已经累计了大约 220 万的阅读人次,受到了不少读者朋友的关注和支持,所以我相信很多人是看过它的在线版本,上面这段话其实就是前言里面的一部分。

今天这篇文章更主要是想分享一下这本书的由来,还有一些关于我、关于写书这些事背后的一些故事,希望感兴趣的读者可以继续支持!

目录详情和购买地址

先放上纸质书的购买地址吧,关于书的目录和内容介绍可以在商品详情查看:

☞ 访问 京东商城 购买

☞ 访问 天猫商城 购买

如果您对我的作品认可,建议购买纸质版,纸质书在电子书的基础上,经过机械工业出版社的编辑老师们的内容优化、校对勘误、排版美化,更成体系,在此特别感谢李晓波编辑对我将开源作品出版为纸质作品的支持,李老师全程帮忙跟进了无数的大事小事,也给我科普了很多出版方面的知识,十分尽职!

这本书怎么样?

在出版纸质书之前,电子版就开通了基于 GitHub Issue 的评论功能,收到了很多读者给我的反馈和交流,摘选了一部分如下(排名不分时间先后):

GitHub 上的读者评论

也有来自宝岛的小姐姐热心安利(我就说某段时间突然有很多来自台湾的工程师关注,然后 Google 了一下发现了这个推,感谢我的热心网友们!

来自宝岛的小姐姐热心安利

因为之前有热心读者问我有没有赞赏渠道,所以我从去年 10 月份在文档上挂了一个赞赏码,可以给我家三只猫猫打赏点罐头,也收到了很多读者的捐赠,有好几笔是大额的赞赏,特别特别感谢!!!

一些赞赏记录

所以关于这本书的内容质量,我相信您看到这里时应该也有了一个大概的了解!

放上我家三只猫,欢迎在线吸猫!

我家的三只猫

我家的三只猫

这本书是怎么写出来的?

作为写了一本大约 32 万字的成品书籍的作者,我一开始并没有很功利的想把它写成一本书,它最早最早的前身其实是我无数日夜在学习过程中的笔记碎片。

刚开始关注和使用 Vue 3 的时候还是 2020 年,那段时间资料很少,相关的官网也只是基于 Vue 2 的内容向上适配了一下,并且只有英文版,其他的随手可得的资料,真的好少好少… 我那段时间也算处于一个比较早期的开荒阶段,遇到问题也只能 Google 和 StackOverflow ,还有在 GitHub 仓库的 Issue 区和源码挖挖看有没有解决方案。

再后来发现还是不太够用,慢慢地又跑去 RFC 仓库 挖掘一些还没有正式公开,但其实已经实现的 “隐藏功能” ,期间也整理过一些文章分享出来过(例如: Vue3.0 最新动态:script-setup 定稿 部分实验性 API 将弃用 , Evan 大佬还在评论区亲自帮忙解答大家的问题)。

因为开荒的过程都是利用本就不多的休息时间,所以早期为了解决各种问题和先把项目做出来,也没有第一时间整理博客笔记,我通常的习惯都是先把找到的资料丢给我一个专门存档临时笔记的微信小号,作为一个可检索的 “便签” 。

当时记录的一些临时笔记

再后来发现实在太多了,这么零散的记录我自己回头也容易忘记,秉着对费曼学习法的多次实践(用输出来倒逼输入,真的特别有效!),逐步整理出了第一个版本,在 2020 年国庆节那会部署到了博客上面,刚上线的时候它还只是一本很纯粹的关于 Vue 3 的入门学习指南,内容比较单一,阅读门槛也比较高(需要本身已经熟悉了 Vue 2 才能看懂我在说什么)。

很感谢第一波读者的鼓励,很热心地给了我评论反馈,还收到了很多邮件交流遇到的问题,经过不断地迭代,慢慢地补充了很多通俗易懂的例子,并且逐步增加了关于前端工程化和 TypeScript 的入门学习内容。

期间还有很多读者自发帮我宣传( e.g. PR #6075 ),让我写的内容逐步被更多的人看到,所以到了后面一共有三家出版社联系了我出版事宜,也才有了后面这些出版相关的事情(因为收到不同出版社的邀请在时间上跨度比较大,所以我也是选择了最早联系我的李晓波编辑和机械工业出版社,在这里也很感谢其他出版社的编辑对我的作品的认可!)。

我是一个什么样的人?

再来说说我自己,我并不是一个很纯粹的程序员。

认识我久一点的朋友也知道我并不是从毕业就一直在做开发,虽然读书那会选了网络专业也勉强算是科班出身(哈哈哈哈我真的不太想提及这些往事,因为那个时候自己也还挺不懂事,老在挂科边缘徘徊,经常这边老师教完知识,过段时间我又还给老师了,反正就是成绩不怎么好的那一类人),相对于做开发或者运维等更重技术的岗位,我那个时候更喜欢打游戏、混各种论坛社区写一些挂机脚本、喜欢看人家怎么做产品设计等东西,所以毕业后跑去游戏行业做了产品运营(我在 UC 的那两年就是做产品运营)。

一开始倒是觉得做运营挺有趣的,但后来发现我的运营 KPI 很大程度依赖于在重要节点的各种活动和宣传合作页面用于推广,但 “拿不到排期” 这个事情总是在阻碍着我达成 KPI ,后来我就跟技术部的同事发起了一个小小的请求:“一些简单的需求页面我自己写,你们有空的时候能不能稍微帮我审查下代码和安排部署?” ,然后我就开始在工作时间干起了写代码的活,我负责的运营业务的数据指标也因为需求能如期上线而基本能按预期跑满,从此就一发不可收拾。

但那段时间我还不敢把自己定位为程序员去换工作,毕竟学的越多越觉得自己渺小,计算机的世界里有太多自己没有接触到的东西,所以 2015 年从 UC 跳槽去网易游戏的时候,我还是去面的运营岗位,进入了大话手游业务线,那段时间刚好面临大话手游即将上线,已经在计划中的需求 1234 是真的多,但又是喜闻乐见的 “没有排期了” ,所以入职后作为一个运营,安排给我的第一个任务是写一个答题器,我就……(此处是 Max 的问号脸.jpg )

Max 的问号脸.jpg

其实是我当时的运营简历也写了我会写页面,在网易的面试过程中也聊到了我在 UC 自己做自己的需求的经历,所以当时的网易总监也知道我可以写… 经过 “一直需求顺利上线一直爽” 的阶段后,再后来就在部门内就成立了自己的技术组、产品组,开始有了自己的前端开发和产品策划等不同岗位,所以从 2016 年开始我就开始正式以写代码为生了,到 2023 年的今年,刚好 7 年。

兜兜转转从技术专业跑去做产品运营,再从运营又杀回来做技术,经常有人问我那几年后不后悔,也有人很好奇我转回技术这段过程难不难。

先说关于难不难,我只想说两个字:“热爱” !当你对一件事情有了足够的热爱,真的没有什么难的。这里的 “热爱” 指的是可以长期保持数年的喜欢不变心,而不是三分钟热度的 “教练我要学” 盲目跟风。

再说说关于后不后悔这件事,至少目前的 7 年里是没有后悔的,我本身从小就不是一个按部就班的人,总是跟着我的兴趣去做我喜欢的事情,青少年时期我的同学都喜欢看球、喜欢听流行歌曲、喜欢唱情歌,而我那个时候就已经是一个享受着孤独沉迷在自己世界里的听摇滚乐的人,从来不看球,从来不听情歌,至于在其他人眼里可能显得比较独来独往和特立独行,从小好像就不是特别在意。

我家境很一般,在我工作之前,我爸爸妈妈每天忙碌养家也只是赚个温饱,所以我基本上是没有零用钱这种东西,虽然早在初中高中那会就想玩乐队,但吉他贝斯等乐器和学琴的费用在当时真的是完全消费不起,但也没有妨碍我对它们一直保持热爱,买不起我就自己做,自己画版型,自己锯木料,自己上油漆,给自己做了最喜欢的 BEYOND 乐队吉他手阿 Paul 在 1991 年演唱会上的那把斯坦伯格的无头吉他模型满足自己内心的 Rock'N Roll ,这可能是我最早期的自己的需求自己实现吧啊哈哈哈。

自己做的吉他模型

工作后身边的同龄人开始恋爱结婚生子学车考驾照,我是完全反着来,因为不喜欢坐车和极少出门,所以我到现在也没有想考驾照的念头,反而在爸妈眼里的 “你也老大不小了(后半句就是你该成家了… )” 的年纪才开始自学弹琴、组乐队玩演出圆我当初的乐手梦,当年第一把琴还是借的,只想着过把瘾,结果发现真的放不下了,到了后面自己也走上了买自己喜欢的琴的不归路。

左边是借的第一把贝斯,右边是自己的琴

再后来有了一些志同道合的伙伴一起夹 Band ,有幸也登上了几次三千多人的舞台,在别人都是唱流行歌曲和谐氛围里,我们又玩起了相对小众的新金属,哪怕现在我同学孩子都已经很大了,我还每天沉迷在 “塞狗、发克鹰、馊兽” 这样的嘶吼音乐里(这是 Slipknot 活结乐队在 Psychosocial 这首歌的某次现场版开场,特别燥的一个 Live 版)…

对于爱好和生活之间的平衡,这一点我特别佩服中山大学的何广平教授,白天衬衫教书,晚上甩头演出,双面人生太让我羡慕了!(可戳: 一边是量子物理,一边是极端金属,中大教授诠释科研与音乐的完美融合 了解何教授)。

用了好久 “胸口碎大石” 这个乐队名

头发也是从 2018 年初就再也没有去剪过,从光头到莫西干再到现在的长发及腰,我感觉我还可以继续留下去…

“程小姐” 你好…

最让我走上 “只过自己喜欢的生活” 的不归路就是下定决心去文身了,从不到 20 岁的时候就想要纹一条花臂,也是念念不忘了多年后终于开始了行动,第一个文身是我的琴,然后是我的猫,然后是音乐元素,还有一些逆境阶段的有意义的图,有一些是自己设计点草图,到了后面默契上来了就全部交给我的专属文身师 Johnny 了,从 2016 年文身到现在,还在继续加图案,每一个图案都在自己的人生阶段里有着不同的意义。

我的花臂

说了这些往事,主要还是想回到前面说的,只要保持足够的 “热爱” ,很多困难真的不是困难,而且因为热爱,也会自然而然的遇到一群志同道合的好朋友,很感谢这么多年一路陪我玩陪我成长以及在我转行过程中给到了很多帮助的同事好友!

如果对我的更多往事感兴趣,可以扫码查看我之前整理的我在网易五年的工作和生活的记录。

记录一下在网易五年来的工作与生活

表达能力如何培养?

最后分享一个可能会比较多程序员都比较关心的问题,如何锻炼自己的码字能力和表达能力。虽然我很宅,不爱社交,但很多读者在阅读了我的文字之后给我的反馈都是看完容易理解,性格和表达似乎出现了矛盾?其实不然。

我之前写过一篇文章叫 Markdown 工程师的一周 ,分享过我曾经一周写了大约 25 个小时的 Markdown 文档,几乎一天有 5 个小时在码字。

一周写了 24 多个小时的文档

为什么我不觉得写文档是个很费劲的事情?因为我自己常常保持的习惯有主要两个:

一个是保持每天写日记的习惯,把自己的喜怒哀乐都记录到日记里,开心的时候记录起来以后可以回顾,不开心的时候用文字把负面情绪输出走,这个方式可以很好的让自己不会把坏情绪带回家里或者带入到工作中,从几个字到两三千字我都写过,内容的多和少并不需要很刻意的去要求,重要的是这一天对你来说,过的有没有意义,我从 2019 年把写日记的习惯重新培养起来到现在也有 4 年了,已经连续写了 4 年日记,没有中断过。

另外一个习惯是上面说的费曼学习法,把自己学到的东西输出成博客文章或者其他什么载体都可以(例如下面的两个 PPT ,是我之前在一些分享会上的演讲稿,已脱敏),一开始写分享内容时可能会觉得无从下手,但是如果能够长期保持习惯,久而久之自己所写的东西就会开始有了逻辑有了条理。

因为上面两个习惯,让我后来开始记录我平时做菜时的菜谱也蛮多人爱看。

左边是我的小红书账号,右边是我曾经写了两千多字的日记

写在最后

能看到这里的朋友对我真的是真爱了,希望我的书也能给到您一些帮助,希望您在继续学习的路上,或者转行的路上,都可以实现自己想达到的目标!

再次附上买书链接:

☞ 访问 京东商城 购买

☞ 访问 天猫商城 购买

如果想问我接下来会做什么?我也是继续保持学习哈哈哈哈哈哈!我在今年三月份刚刚换了新工作,来到了一个很牛逼的团队,技术氛围特别好,队友们的能力也好强,未来也有很多我之前没有接触的东西,我也还在继续成长!

之前跟朋友说的哈哈哈

最后的最后,分享一下我保持学习的动力和精力的秘诀……

一年多前觉得是这个原因

现在还是觉得是这个原因

谢谢您的支持!

Git的选择性合并操作笔记:合并某个版本或某个提交

[[toc]]

今天帮朋友解决了一个代码合并的问题,他有两个项目, B 项目最初是基于 A 项目作为架构底子,根据业务进行了不同需求的开发,沉淀了不少新功能,而 A 项目本身也在继续维护,可以简单的理解为, A 项目是通过类似 create-preset 这样的脚手架拉取下来的一个项目模板,而 B 项目是一个业务项目,所以 A 项目通常只提供一些公共功能的维护升级,而 B 项目更注重业务功能开发。

项目 作用
A 项目 基础模板,提供一些公共功能的维护升级
B 项目 业务项目,在 A 项目的基础上,围绕具体业务进行功能开发

因此 B 项目想升级 A 项目的功能,并不是不可能,冲突也不一定会很多。不过因为不是每个版本都升级,所以如果手动合并代码,工作量又比较大。分享了几个方案给他尝试,当然这些操作对我来说也不怎么常用,所以记录起来,以后自己用到也不用重新踩坑。

下面的演示都基于同一个 Git 仓库的不同分支,实际处理中,可以选择把 B 项目放到 A 项目的某个分支来实现合并,也可以通过 git remote add <shortname> <url> ,将两个项目关联到同一个 Git 远程仓库。

合并某个版本

先看看一个版本合并的操作,假设 B 项目不想完全升级到 A 项目的最新版本(可能因为最新版本是一个包含破坏性升级的 Major 版本,出现了很多不兼容的情况),那么可以选择其中一个 Minor 次要版本进行升级。

处理思路

由于并不是每个项目都有打 Tag ,所以可以选择指定某个 Commit Hash 作为临界点。在这个例子里,将 A 项目在某个 commit 记录之前的功能,都需要同步给 B 项目,这里使用 git merge <commit_hash> 命令来实现。

具体操作

随便拉取了一个旧的版本,拉取深度为 10 (主要参数是 --depth 10 ,其他的 --branch--single-branch 可以视情况指定,这里只是为了单纯拉取 main 分支的代码)。

git clone https://github.com/chengpeiquan/test.git --branch main --single-branch --depth 10

进入仓库文件夹,使用 git log 命令查看本地仓库和远程仓库的 main 分支差异(这里使用了 --oneline 参数简化了提交记录的展示)。

cd test
git log --oneline main origin/main

看一下 Log ,确实是 10 条 Commit 差异:

# git log --oneline main origin/main
3ad0947 (HEAD -> main, origin/main, origin/HEAD) chore: backup
fc322fe chore: backup
1cc6c01 chore: backup
4898c9c chore: backup
8258834 chore: backup
cb7d560 chore: backup
10b3bb5 chore: backup
2119218 chore: backup
1c0c5ba feat: backup
f49e8b0 (grafted) chore: backup

将本地的 Commit 记录重置到拉取时的那个 Commit ,如果不执行这一步,在 Merge 的时候会提示 Already up-to-date. 的信息,无法合并。

git reset --hard f49e8b0

在 10 条 Commit 差异里,现在使用 git merge <commit_hash> 命令合并第五条 Commit (它的 Hash 是 cb7d560 ),保留 5 条差异。

git merge cb7d560

合并成功反馈:

# git merge cb7d560
Updating f49e8b0..cb7d560
Fast-forward
README.md | 14 +++++++++++---
docs/introduction.md | 1 -
docs/zh/introduction.md | 1 -
3 files changed, 11 insertions(+), 5 deletions(-)
delete mode 100644 docs/introduction.md
delete mode 100644 docs/zh/introduction.md

再次查看本地 main 分支和远程 main 分支的差异:

git log --oneline main origin/main

本地的 HEAD 已经在第五条了:

# git log --oneline main origin/main
3ad0947 (origin/main, origin/HEAD) chore: backup
fc322fe chore: backup
1cc6c01 chore: backup
4898c9c chore: backup
8258834 chore: backup
cb7d560 (HEAD -> main) chore: backup
10b3bb5 chore: backup
2119218 chore: backup
1c0c5ba feat: backup
f49e8b0 (grafted) chore: backup

为了避免覆盖,创建一个新分支来提交刚刚本地合并后的代码:

git checkout -b dev

再次检查一下新分支 dev 分支和远程 main 分支的差异:

git log --oneline dev origin/main

确认没有问题(因为是从本地 main 分支创建的,所以代码是一样的,但在进行敏感操作之前,还是养成一个二次检查的习惯)。

# git log --oneline dev origin/main
3ad0947 (origin/main, origin/HEAD) chore: backup
fc322fe chore: backup
1cc6c01 chore: backup
4898c9c chore: backup
8258834 chore: backup
cb7d560 (HEAD -> dev, main) chore: backup
10b3bb5 chore: backup
2119218 chore: backup
1c0c5ba feat: backup
f49e8b0 (grafted) chore: backup

可以提交到远程的 dev 分支了(也就是实际上 B 项目代码所在的分支)。

git push -u origin dev

main 分支的提交记录

dev 分支的提交记录

合并某个提交

有时候仅仅想单独合并某一条或者某几条 Commit 的改动,不希望包含该 Commit 之前的其他 Commit ,可以选择这个方案来处理。

处理思路

合并某个版本 的操作不同,这种情况会产生新的 Commit Hash ,因此整个提交历史会被打乱,所以比较适合简单的代码合并,例如某个模块新增了功能,而其他的功能并不需要,只想要这个模板的新功能,那么选择这个方案会比较合适。

这个方案就不再是使用 git merge 了,而是使用 git cherry-pick <commit_hash> 命令来实现。

具体操作

继续在 dev 分支上操作,也是先查看差异:

git log --oneline dev origin/main

由于前面 合并某个版本 的操作是合并第五个 Commit 以及之前的提交记录,因此还有 5 个未同步的记录。

# git log --oneline dev origin/main
3ad0947 (origin/main, origin/HEAD) chore: backup
fc322fe chore: backup
1cc6c01 chore: backup
4898c9c chore: backup
8258834 chore: backup
cb7d560 (HEAD -> dev, main) chore: backup
10b3bb5 chore: backup
2119218 chore: backup
1c0c5ba feat: backup
f49e8b0 (grafted) chore: backup

这里挑选未同步的记录里面的一个,只合并这个:

git cherry-pick 4898c9c

合并成功反馈:

# git cherry-pick 4898c9c
Auto-merging README.md
[dev 3f8b616] chore: backup
Date: Mon Dec 5 13:54:43 2022 +0800
1 file changed, 5 insertions(+), 1 deletion(-)

现在重新查看本地 dev 分支和远程 main 分支的差异,可以看到多出来一个新的提交了。

# git log --oneline dev origin/main
3f8b616 (HEAD -> dev) chore: backup
3ad0947 (origin/main, origin/HEAD) chore: backup
fc322fe chore: backup
1cc6c01 chore: backup
4898c9c chore: backup
8258834 chore: backup
cb7d560 (main) chore: backup
10b3bb5 chore: backup
2119218 chore: backup
1c0c5ba feat: backup
f49e8b0 (grafted) chore: backup

其他补充

在演示项目上操作通常还是很顺利的,但实际项目里可能会存在代码冲突,建议在操作时,按照小版本的提交去合并同步,减少冲突的解决成本。

年终总结:2022年的一些回顾和2023年的一些小规划

[[toc]]

2022 对我算是比较特殊的一年,虽然因为疫情原因彻底宅了一年,但也没有闲着,换了工作换了城市,回到了阔别两年的第二故乡广州,工作之外的学习和生活对个人的成长也有一些值得复盘的地方。

工作与生活

2020 年疫情爆发那年发生了很多事情,最终也决定从服务了五年的网易公司离职(见 记录一下在网易五年来的工作与生活),随即和几个朋友跟着大佬去了深圳,因为大家都是扎根广州,所以都把深圳当成一个临时过渡的城市,所以今年时机成熟的时候就又一起回到了广州。

回广州后也终于不用再苟在城中村里了,在依山傍水的山景房里,三只猫也迎来了久违的阳光,每天睡到自然醒,醒了有东西吃有玩具玩,还不用出门工作,过的比我还幸福。

我从五月底回来后就一直很忙,因为广州这边也是刚成立的新公司,太多事情要忙,加上业余时间也有其他事情, 菜谱 也慢慢停更了,不过偶尔还是有在做饭,新的一年看看调整下生活节奏重新运营起来。

虽然工作很忙碌,但在初创公司的日子里倒也是很有趣,因为人少(目前加上老板还不到十五个人),所以自己也有机会开始承担起一些前端工程师之外的开发工作,例如写 App ,写客户端,写服务端,写爬虫,写区块链,当然都是围绕着 Node.js 进行的全栈开发。

写着写着就没有什么时间写页面了,所以今年也是把 Tailwind CSS 这一类原子框架大幅度的往工作项目里用,这样在需要写页面的时候可以不怎么写样式了,对 Flex 布局熟悉的前端工程师来说, Tailwind 风格的开发模式做东西真的非常快,很适合初创公司老板说 “啊啊啊这个东西很着急” 然后第二天就可以给到他。

这一年是真的忙碌(见下文的 开源社区 部分),以至于几乎没有什么运动量,可能也因为运动太少,所以从九月份开始,从痛风发作到新冠病毒,陆陆续续当了一个季度的病号,不过有一点算是不幸中的万幸,就是因为一直宅着,所以年底新冠阳性的时候仅仅只是个弱阳,低烧了一个晚上就开始在恢复健康了,相对于在朋友圈看到一些朋友病得五颜六色,我还是挺幸运的。

另外关于生活的话,就是头发达到了历史上的最新长度,往后甩的时候已经过了腰了,虽然发质一如既往的差,但我并不关心发质哈哈哈哈,长度才是王道。

头发达到了新的长度

开源社区

今年在 GitHub 的活跃度比去年高了不少,而且很神奇的达到了全勤,因为疫情的原因,还有换城市和新工作等事情,今年也没有出远门或者回家待几天,每天都在有电脑的屋子里待着,所以每天都有在 GitHub 上操作(截图生成自 GitHub Contributions ),也归功于今年把很多有的没的代码都托管到 GitHub 上了,包括一些 to-do list 也会有个 Markdown 仓库作为临时笔记,很方便哈哈哈。

这两年在 GitHub 的活跃情况

虽然这么活跃,但开源项目其实没有参与多少,基本上都在维护去年的一些旧项目,其中投入最多的是之前写的那本关于 Vue 3 的书 Learning Vue3

今年一月份的时候在知乎收到一条私信,是出版社的编辑发现了我写的东西,邀请我参与到书籍的出版工作中。当时由于临近过年比较忙,没有立即答复是否参加,后面四月份编辑老师又联系了我,在考虑了自己的时间和目前手里头比较有积累有篇幅的内容后,咨询了是否可以用现有的开源内容整理出版(还举了阮一峰老师的 ES6 入门教程 的例子),得到了肯定的答复之后,就开始安排时间调整 Learning Vue3 的内容。

由于当时 Vue 3 已经成为 npm 安装时的默认版本,并且中文官网也建设起来了,考虑到继续单独讲述 Vue 3 的内容显得有点 “过时” (毕竟当时写这本书的背景是国内没有什么 Vue 3 的资料,并且还处于公测但未完全替代 Vue 2 的阶段),在出版社编辑老师的建议下,续写了关于前端工程化的内容,以降低本书的学习门槛,所以可以看到我从 四月底的更新记录 就慢慢不再局限于 Vue 了,而是一直在更新前端工程化的内容。

再之后的几个月就一直在补充新内容,并且按照出版社的供稿规范调整了叙述风格,最终整个文档的风格就变得比较有 “专业性” ,不再是嘻嘻哈哈的逗逼语气,自己感觉有点像阮一峰老师,确实在阮老师的博客里受到了很大影响。

很高兴续写的内容得到了线上读者的认可,不仅仓库从 100 Star 慢慢涨到了现在接近 500 Star ,还有很多读者给我留言,给了我很大的鼓励!

仓库接近 500 Star 了

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

在 GitHub 仓库里的评论

调整后的叙述风格也被读者认可,在 2022 年底突然有一群台湾开发者关注了我的 GitHub 账号,搜索了一下才发现原来 被台湾一位技术大 V 推荐了 ,她很喜欢我的风格,我很开心哈哈哈,付出得到了回报,得到了认可!

台湾网友的分享

其他的开源项目大部分是一些 npm 包,总下载量也突破 35 万次了(数据来源 npm-stat )。

来自 npm-stat 网站的统计数据

有个 Vite 插件被大佬的项目引入后带火了,现在每周大约保持在 7k 左右的下载量,也收到了其他开发者的 PR 贡献( #16 ),期间认真的评审了代码和给予了意见点评,直到那天才感觉在认真参与开源项目协作,在此之前都是自己单打独斗,或者发起一些没有什么修改意见的翻译 PR ,很少这样在 PR 过程中讨论功能的取舍。

在 PR 过程中讨论功能的取舍

还有一个使用量比较多的插件是一年多前做的一个自用的小工具,很久没维护,结果一直有人用,前段时间还提了一些反馈,所以年底有空于是重新维护了起来,还用 Photoshop 鼠绘了个 Logo ,偶尔设计点东西还挺有意思的,虽然渣渣,但我觉得很贴合用途哈哈哈。

新官网上挂着自己画的 Logo

另外还有一点比较值得高兴的是,新开坑的开源仓库都已经全部使用英语了,包括代码里的注释,还有 issue 的交流,虽然语法和用词什么的不一定对,但在 Google 翻译校验了一下还是可以识别的,多读多写多练习,总有一天可以不太依赖翻译工具的。

新年展望

刚好很多事情都在 2022 年底弄完了,新的一年业余时间应该不会那么忙了,我要多运动,多爬山,明明现在住的地方对面就是一座山,我居然一次都没有爬过,真是浪费啊浪费可耻哈哈哈。

底迪很喜欢在阳台看山景

然后把三年没弹的贝斯重新弹起来,太久没有弹琴了,自从去了深圳,总感觉人在漂泊,以至于从 2020 年之后就没有再弹过琴,希望今年可以重新积累一些可以演奏的曲子。

技术方面,长远的规划不好说,梳理了一下最近几个月的计划吧:

  1. 重构博客(上一次是 2020 年 1 月,也有两年了),技术选型还是偏向于比较新的前端技术栈,找个时间调研一下
  2. 认真学一下 React ,其实很久前就有想法,但因为缺乏可落地的个人项目,所以一直没动手,只是简单的写了几个 demo ,去年写 App 的时候因为主要技术栈还是 Vue ,所以选了 uni-app ,但感觉体验并不是太好,把 React 玩熟悉之后以后也可以切入 React Native 开发
  3. 重构之前写的命令行工具 create-preset ,一个模板拉取工具,现在在用的 GitHub 镜像挂掉了一直没去修,打算把一些自己维护的模板处理成本地一起发包,不走 GitHub 下载了,其他人的社区模板就继续拉仓库,不过要考虑现有的 API 兼容,只能是无感知的重构,感觉要做的事情也不少

其他的可能就是 Web 3 方面的开发继续深入吧,智能合约和 dApp ,也是未来的趋势,并且基本上都是以前端的技术栈为主,作为前端工程师,天生有前端优势的情况下,不玩下区块链多可惜!

❌
❌