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

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

liuian 2025-03-13 17:16 12 浏览

因缘

不久前打算将我的博客内容格式从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/commonmakeTransformer,修改传入的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

相关推荐

2023年最新微信小程序抓包教程(微信小程序 抓包)

声明:本公众号大部分文章来自作者日常学习笔记,部分文章经作者授权及其他公众号白名单转载。未经授权严禁转载。如需转载,请联系开百。请不要利用文章中的相关技术从事非法测试。由此产生的任何不良后果与文...

测试人员必看的软件测试面试文档(软件测试面试怎么说)

前言又到了毕业季,我们将会迎来许多需要面试的小伙伴,在这里呢笔者给从事软件测试的小伙伴准备了一份顶级的面试文档。1、什么是bug?bug由哪些字段(要素)组成?1)将在电脑系统或程序中,隐藏着的...

复活,视频号一键下载,有手就会,长期更新(2023-12-21)

视频号下载的话题,也算是流量密码了。但也是比较麻烦的问题,频频失效不说,使用方法也难以入手。今天,奶酪就来讲讲视频号下载的新方案,更关键的是,它们有手就会有用,最后一个方法万能。实测2023-12-...

新款HTTP代理抓包工具Proxyman(界面美观、功能强大)

不论是普通的前后端开发人员,还是做爬虫、逆向的爬虫工程师和安全逆向工程,必不可少会使用的一种工具就是HTTP抓包工具。说到抓包工具,脱口而出的肯定是浏览器F12开发者调试界面、Charles(青花瓷)...

使用Charles工具对手机进行HTTPS抓包

本次用到的工具:Charles、雷电模拟器。比较常用的抓包工具有fiddler和Charles,今天讲Charles如何对手机端的HTTS包进行抓包。fiddler抓包工具不做讲解,网上有很多fidd...

苹果手机下载 TikTok 旧版本安装包教程

目前苹果手机能在国内免拔卡使用的TikTok版本只有21.1.0版本,而AppStore是高于21.1.0版本,本次教程就是解决如何下载TikTok旧版本安装包。前期准备准备美区...

【0基础学爬虫】爬虫基础之抓包工具的使用

大数据时代,各行各业对数据采集的需求日益增多,网络爬虫的运用也更为广泛,越来越多的人开始学习网络爬虫这项技术,K哥爬虫此前已经推出不少爬虫进阶、逆向相关文章,为实现从易到难全方位覆盖,特设【0基础学爬...

防止应用调试分析IP被扫描加固实战教程

防止应用调试分析IP被扫描加固实战教程一、概述在当今数字化时代,应用程序的安全性已成为开发者关注的焦点。特别是在应用调试过程中,保护应用的网络安全显得尤为重要。为了防止应用调试过程中IP被扫描和潜在的...

一文了解 Telerik Test Studio 测试神器

1.简介TelerikTestStudio(以下称TestStudio)是一个易于使用的自动化测试工具,可用于Web、WPF应用的界面功能测试,也可以用于API测试,以及负载和性能测试。Te...

HLS实战之Wireshark抓包分析(wireshark抓包总结)

0.引言Wireshark(前称Ethereal)是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口,直接...

信息安全之HTTPS协议详解(加密方式、证书原理、中间人攻击 )

HTTPS协议详解(加密方式、证书原理、中间人攻击)HTTPS协议的加密方式有哪些?HTTPS证书的原理是什么?如何防止中间人攻击?一:HTTPS基本介绍:1.HTTPS是什么:HTTPS也是一个...

Fiddler 怎么抓取手机APP:抖音、小程序、小红书数据接口

使用Fiddler抓取移动应用程序(APP)的数据接口需要进行以下步骤:首先,确保手机与计算机连接在同一网络下。在计算机上安装Fiddler工具,并打开它。将手机的代理设置为Fiddler代理。具体方...

python爬虫教程:教你通过 Fiddler 进行手机抓包

今天要说说怎么在我们的手机抓包有时候我们想对请求的数据或者响应的数据进行篡改怎么做呢?我们经常在用的手机手机里面的数据怎么对它抓包呢?那么...接下来就是学习python的正确姿势我们要用到一款强...

Fiddler入门教程全家桶,建议收藏

学习Fiddler工具之前,我们先了解一下Fiddler工具的特点,Fiddler能做什么?如何使用Fidder捕获数据包、修改请求、模拟客户端向服务端发送请求、实施越权的安全性测试等相关知识。本章节...

fiddler如何抓取https请求实现手机抓包(100%成功解决)

一、HTTP协议和HTTPS协议。(1)HTTPS协议=HTTP协议+SSL协议,默认端口:443(2)HTTP协议(HyperTextTransferProtocol):超文本传输协议。默认...