All Articles

Obsidian插件经验

1. 前言

之前花了点时间制作Obsidian的插件:agreatfool/obsidian-post-gallery,稍微积累了点经验,这里留点笔记。不会过于深入,因为我做的插件还是很简单的,也没有用到太多的Obsidian的功能。

丑话说在前面,Obsidian的Plugin相关文档是极其匮乏的,有很多功能你基本上找不到文档对于其的描述,更扯的是一部分概念在文档里也没解释,比如说:leaf等,导致如果你需要制作功能比较复杂的插件的话,会很困难。最好的方法就是找现在官方网站上列出来的那些community plugin的源码,然后慢慢读。我也搞不懂这些作者是怎么搞清楚那么多细节的,哎。反正我是没做很高级的功能,所以基本上没怎么接触。

2. 基础

obsidianmd/obsidian-api,算是官方的插件文档地址。这个repo里面就只有一个d.ts文件,描述了Obsidian API的内容。制作插件的时候可以使用这些官方提供的API,实际上Obsidian是基于Electron的一个封装,但官方的API并不会暴露Electron的API给你使用,是基于安全方面的考量:Security of the plugins

obsidianmd/obsidian-sample-plugin是官方的范例repo,可以clone下来尝试下,里面有最简单的使用范例。

在编码方面,你会需要安装Obsidian API,虽然本质上它也就只是一个typing。

3. 代码

main.ts

import * as LibOs from 'os';
import { MarkdownPostProcessorContext, Plugin } from 'obsidian';
import { MarkdownBlockProcessor } from './lib/md_block';

export default class InPostGalleryPlugin extends Plugin {
  async onload(): Promise<void> {
    if (LibOs.platform() !== 'darwin') {
      console.log(`InPostGalleryPlugin.onload unsupported platform: ${LibOs.platform()}`);
      return;
    }
    console.log('InPostGalleryPlugin.onload');

    this.registerMarkdownCodeBlockProcessor('post-gallery', async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
      await new MarkdownBlockProcessor(this.app).process(source, el, ctx);
    });
  }
}

定义一个 default export 的 class,继承 obsidian 包里的 Plugin。上面的代码就只响应了onload事件,在事件中保证插件安装在OSX系统中,并注册一个markdown代码块处理器,在post-gallery代码块出现的时候进行触发。

md_block.ts

剩下的其他功能代码也非常简单,一共没几行:md_block.ts,可以简单看下。

4. 问题

当时在制作这个插件的时候,虽然功能很简单,但还是遇到了不少问题。最主要的一个问题是代码打包。

因为所有的dep安装(源码)都是通过npm安装的,而代码的import / require又是在Obsidian (Electron)环境中运行的,所以很多用来初始化的self execute function: (function(){})()会侦测错环境,导致最终的lib加载注入错了global:global vs window

这里需要使用第三方的打包工具来进行代码打包,比如说webpack,但因为我这个项目实在是太小了,杀鸡牛刀,也不想浪费时间搞一大堆的配置。最后就直接把一些第三方的dep的加载函数给改了下,放到src里,作为自己的源码来进行import,就行了。算是比较tricky的绕过方法。

举个例子:

原版 jquery

(function (global, factory) {
  'use strict';

  if (typeof module === 'object' && typeof module.exports === 'object') {
    // For CommonJS and CommonJS-like environments where a proper `window`
    // is present, execute the factory and get jQuery.
    // For environments that do not have a `window` with a `document`
    // (such as Node.js), expose a factory as module.exports.
    // This accentuates the need for the creation of a real `window`.
    // e.g. var jQuery = require("jquery")(window);
    // See ticket #14549 for more info.
    module.exports = global.document
      ? factory(global, true)
      : function (w) {
          if (!w.document) {
            throw new Error('jQuery requires a window with a document');
          }
          return factory(w);
        };
  } else {
    factory(global);
  }

  // Pass this if window is not defined yet
})(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
  // ...
});

修改后

(function (global, factory) {
  'use strict';

  module.exports = factory(window);

  // Pass this if window is not defined yet
})(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
  // ...
});

Reference

EOF

Published 2021/10/8

Some tech & personal blog posts