百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

关于写一个NodeJS库补上org生态缺失的一环这件事

bigegpt 2025-02-20 14:33 7 浏览

因缘

不久前打算将我的博客内容格式从mdx?转到?orgmode?,此前我一直在使用contentlayer[1]管理我的mdx文档,但是因为一些原因这个项目停止维护了,并且虽然它具有一定的定制化能力,但和Markdown的绑定太深,无法满足我迁移到orgmode的需求,于是我花了点时间做了我个人的第一个JavaScript/TypeScript库:docube[2]

设计

通常我更喜欢使用可定制性高的软件,但是像Vim、Emacs这类软件常常被抱怨新手上手难度太高,似乎高度可定制和开箱即用是非常冲突的理念,所以我希望做一个硬核用户可以自定义行为,普通用户又可以快速上手使用的应用。在具体实践上,我借助了effect[3]这个库,抽象出了一个通用的转换流程:



我的原始的需求就是将本地的org文件读取解析成HTML文本格式,并和其它元数据一起组成JSON文件+TypeScript定义文件的形式,之后在React里直接引用。核心的流程就是通过?Loader?获取抽象的?FileLkie[]?,再调用?FileConverter?转换内容,最后通过?Writer?写入,因为我想尽量保持核心的通用性,所以?ModuleResolver?(主要是用来生成JS模块和类型定义)在这里是可选的,用户可以通过注入对应的依赖来改变默认的行为。

常规的使用方式并不需要了解这些概念,下面是这段是我的博客从contentlayer迁移后的代码:

import { transform } from '@docube/mdx'
import rehypeProbeImageSize from './lib/rehypeImage'
import remarkGfm from 'remark-gfm'

transform({
  name: 'Post',
  directory: './posts',
  include: '**/*.mdx',
  fields: (s) => ({
    title: s.String,
    tags: s.Array(s.String),
    series: s.String,
    createdAt: s.String,
    publishedAt: s.String,
    summary: s.String,
  }),
  remarkPlugins: [remarkGfm],
  rehypePlugins: [rehypeProbeImageSize],
})

执行这段代码就可以得到一个生成的?.docube/generated/posts?模块,顶层导出了?allPosts?变量,在NextJS里,可以这样使用[4]

import { allPosts } from '@docube/generated'
import { getMDXComponent } from 'mdx-bundler/client'

// ...
  const MDXContent = getMDXComponent(post.body)
// ...

// ...
export async function generateMetadata({ params }: Props): Promise {
  const { slug } = params
  // post即是自动生成的Post类型
  const post = allPosts.find((post) => post._meta.slug === slug)

  if (!post) notFound()

  return {
    title: `${post.title} - Elliot`,
    keywords: post.tags as string[],
    description: post.summary,
  }
}

而如果需要个性化使用,如提供一种新的文本格式的支持,只需要引用?@docube/common?的?makeTransformer?,修改传入的FileConverter依赖就可实现,具体见@docube/markdown[5]的实现。

问题

虽说我已经写过不少TypeScript代码,但在npm上发布库还是第一次,过程中还是遇到了不少问题的,在此记录一下,避免后来人踩坑。

Monorepo

考虑到我至少需要默认支持mdx和org两种格式,所以一开始我就想要创建多个库,因此采用了monorepo的形式。Monorepo说白了就是在一个代码仓库里包含有关联的多个项目,可以共享同样的外围工具如lint、format等,项目之间需要重构更新依赖相对来说要比多仓库轻松些。

对于JS项目,在根目录的package.json添加如?"workspaces": ["packages/*"]?,就可以在packages目录里包含多个子包。但是在开发时,如果B包依赖A包,?tsserver?实际上检查的是A包build后的dist,而不是A包的TS代码,也就是说如果A包更新了,需要先build一下,才能使LSP正确地工作。如果不想手动执行命令,可以用一些工具的?Watch Mode?功能,检测到包变化自动rebuild,当然前提是开发机器内存够用:)。

同步依赖

多个子项目依赖同一个依赖的情况是非常常见的,一般来说最好能全局共享这种相同的依赖,将其保持在一个相同版本。这方面NPM那边没有定义这个功能,不像?Cargo?可以让子项目继承Workspace的依赖。要实现这个目的的话,要么用syncpack[6]这类专门处理这个问题的工具,要么用?pnpm?这类的包管理工具的Workspace支持[7]

发版

将包发布到npm上只需要build后执行?npm publish?就可以了,但是如果更新的包被另外几个包依赖了,那么后者也需要更新。这个问题有个辅助工具changesets[8],它能自动帮助更新相关有改动的包的版本,并维护?Changelog?。

scope

NPM有一个比较好的设计是你可以给包名加一个范围前缀,比如有个通用的名字叫time,不同的组织可以用?@google/time?、?@microsoft/time?,一方面是避免想用的名字被抢,一方面是对于大企业来说可以标识一下这是自己的官方包。这里对新手的一个坑点是,当你创建了一个scope,然后想发布一个包,如?@docube/mdx?,默认情况下这个包会被当做是你组织下的私有包,而私有包是要收费的,需要用?npm publish --access=public?明确表明这是个公开的包,或者在package.json里写明:

{
  "publishConfig": {
    "access": "public"
  },
  ...
}

lint

turbo?默认生成的Monorepo模板内部使用了eslint v8?,而当前最新的eslint版本是?v9?,这两个版本之间有不兼容的改动,所以如果在这个模板上新建项目,并且不指定安装的eslint版本的话,将无法使用?turbo lint?命令,解决办法一个是安装eslint时指定使用v8版本,另一个详见我的配置[9]

可选依赖

我本人对软件使用有一点小洁癖,不会用到的依赖就尽量不想要装到我的电脑上。如在Markdown支持上,很多人会在Markdown文件的开头放上一段?yaml?格式的文本来提供一些如撰写时间、作者等元信息:

---
date: 2024-02-02T04:14:54-08:00
draft: false
params:
  author: John Smith
title: Example
weight: 10
---

...

这个被称为?front matter?,但是处理这段文本的库每个人可能有不同的偏好选择(NPM上下载量较大的两个都有三年以上没有更新了);并且有些情况下,这个front matter不一定是yaml格式,如静态站生成器hugo就提供了yaml、toml和json三种选择。

如果我在我的库里直接依赖一个实现,那么既便我为用户提供了自定义解析这段文本的配置,用户也必须下载一个他用不到的第三方库,甚至就算是不需要front matter的用户也不得不安装。为此我使用了可选依赖,可选依赖定义在package.json的?optionalDependencies?,我在开发中使用的是bun,使用?bun add gray-matter --optional?就可以将这个?gray-matter?包安装为可选模式。

在我的库代码里,可以用?tye-catch=加?=import?来判断用户有没有安装我默认的依赖,大致逻辑如下:

if (options.frontMatterExtractor) {
    frontMatterData = options.frontMatterData(content)
} else {
    try {
        const matter = import("gray-matter")
        // ...
    } catch (e) {
        // ...
    }
}

不想要front matter的用户,或者想用自己的逻辑处理的用户,可以用?npm install --omit=optinal?来避免安装我默认的可选包(具体命令根据使用的包管理器不同)。

这篇博客就是我用org格式写的(’?’)

引用链接

[1] contentlayer: https://contentlayer.dev/
[2] docube:
https://codeberg.org/Elliot00/docube
[3] effect:
https://effect.website/
[4] 这样使用:
https://github.com/Eliot00/elliot00.com/blob/master/app/posts/%5Bslug%5D/page.tsx
[5] @docube/markdown:
https://codeberg.org/Elliot00/docube/src/branch/main/packages/markdown/src/index.ts
[6] syncpack:
https://www.npmjs.com/package/syncpack
[7] Workspace支持:
https://pnpm.io/cli/update#--recursive--r
[8] changesets:
https://github.com/changesets/changesets
[9] 我的配置:
https://codeberg.org/Elliot00/docube/src/branch/main/packages/eslint-config

相关推荐

Linux 系统启动完整流程

一、启动系统流程简介如上图,简述系统启动的大概流程:1:硬件引导UEFi或BIOS初始化,运行POST开机自检2:grub2引导阶段系统固件会从MBR中读取启动加载器,然后将控制权交给启动加载器GRU...

超专业解析!10分钟带你搞懂Linux中直接I/O原理

我们先看一张图:这张图大体上描述了Linux系统上,应用程序对磁盘上的文件进行读写时,从上到下经历了哪些事情。这篇文章就以这张图为基础,介绍Linux在I/O上做了哪些事情。文件系统什么是...

linux入门系列12--磁盘管理之分区、格式化与挂载

前面系列文章讲解了VI编辑器、常用命令、防火墙及网络服务管理,本篇将讲解磁盘管理相关知识。本文将会介绍大量的Linux命令,其中有一部分在“linux入门系列5--新手必会的linux命令”一文中已经...

Linux环境下如何设置多个交叉编译工具链?

常见的Linux操作系统都可以通过包管理器安装交叉编译工具链,比如Ubuntu环境下使用如下命令安装gcc交叉编译器:sudoapt-getinstallgcc-arm-linux-gnueab...

可算是有文章,把Linux零拷贝技术讲透彻了

阅读本文大概需要6.0分钟。作者:卡巴拉的树链接:https://dwz.cn/BaQWWtmh本文探讨Linux中主要的几种零拷贝技术以及零拷贝技术适用的场景。为了迅速建立起零拷贝的概念...

linux软链接的创建、删除和更新

大家都知道,有的时候,我们为了省下空间,都会使用链接的方式来进行引用操作。同样的,在系统级别也有。在Windows系列中,我们称其为快捷方式,在Linux中我们称其为链接(基本上都差不多了,其中可能...

Linux 中最容易被黑客动手脚的关键目录

在Linux系统中,黑客攻击后常会针对关键目录和文件进行修改以实现持久化、提权或隐藏恶意活动。本文介绍下黑客最常修改的目录及其手法。一、/etc目录关键文件有:/etc/passwd和/et...

linux之间传文件命令之Rsync傻瓜式教程

1.前言linux之间传文件命令用什么命令?本文介绍一种最常用,也是功能强大的文件同步和传输工具Rsync,本文提供详细傻瓜式教程。在本教程中,我们将通过实际使用案例和最常见的rsync选项的详细说...

Linux下删除目录符号链接的方法

技术背景在Linux系统中,符号链接(symlink)是一种特殊的文件,它指向另一个文件或目录。有时候,我们可能需要删除符号链接,但保留其指向的目标目录。然而,在删除符号链接时可能会遇到一些问题,例如...

阿里云国际站注册教程:aa云服务器怎么远程链接?

在全球化的今天,互联网带给我们无以计数的便利,而云服务器则是其中的重要基础设施之一。这篇文章将围绕阿里云国际站注册、aa云服务器如何远程链接,以及服务器安全防护如Ddos防火墙、网站应用防护waf防火...

Linux 5.16 网络子系统大范围升级 多个新适配器驱动加入

Linux在数据中心中占主导地位,因此每个内核升级周期的网络子系统变化仍然相当活跃。Linux5.16也不例外,周一最新与网络相关的更新加入了大量的驱动和新规范的支持。一个较新硬件的驱动是Realt...

搭建局域网文件共享服务(Samba),手机电脑都能看喜欢的影视剧

作为一名影视爱好者,为了方便地观看自己喜欢的影视作品,在家里搞一个专门用来存放电影的服务器是有必要的。蚁哥选则用一台Ubuntu系统的电脑做为服务器,共享影音文件,其他同一个局域网内的电脑或手机可以...

分享一个实用脚本—centos7系统巡检

概述这周闲得慌,就根据需求写了差不多20个脚本(部分是之前分享过的做了一些改进),今天主要分享一个给平时运维人员用的centos7系统巡检的脚本,或者排查问题检查系统情况也可以用..实用脚本#!/bi...

Linux 中创建符号链接的方法

技术背景在Linux系统里,符号链接(SymbolicLink),也被叫做软链接(SoftLink),是一种特殊的文件,它指向另一个文件或者目录。符号链接为文件和目录的管理带来了极大的便利,比...

一文掌握 Linux 符号链接

符号链接(SymbolicLink),通常被称为“软链接”,是Linux文件系统中一种强大而灵活的工具。它允许用户创建指向文件或目录的“快捷方式”,不仅简化了文件管理,还在系统配置、软件开发和日...