You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Ruby脚本中运行Node.js服务器并通过KaTeX渲染LaTeX?

问题分析

你当前遇到的核心问题是ExecJS在Ruby环境中调用KaTeX的性能瓶颈——每次渲染都要通过ExecJS编译/调用JS环境,导致整体构建耗时飙升。而基于文件读写的Node.js方案虽然快,但频繁的磁盘IO既不优雅也有潜在的性能损耗(比如文件锁、IO延迟)。

优雅的解决方案:Ruby与Node.js进程间管道通信

我们可以让Ruby直接启动一个Node.js子进程,通过标准输入/输出管道进行通信,完全避免文件读写。这种方式既保留了Node.js运行KaTeX的高性能,又能实现Ruby与Node之间的高效、低开销通信。

下面分两部分实现:


1. Node.js 渲染脚本(scripts/katex_renderer.js)

这个脚本会一直运行,监听标准输入,接收Ruby发送的渲染请求,处理后将结果输出到标准输出:

const katex = require('./katex.min.js');
const readline = require('readline');

// 用readline逐行读取Ruby发送的JSON请求
const inputReader = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false // 非终端模式,避免额外输出干扰
});

inputReader.on('line', (rawRequest) => {
  try {
    // 解析Ruby发送的JSON请求
    const { formula, displayMode } = JSON.parse(rawRequest);
    
    // 调用KaTeX渲染,可根据需求调整配置
    const renderedHtml = katex.renderToString(formula, {
      displayMode: !!displayMode,
      throwOnError: false, // 渲染失败时不中断,返回原公式+提示
      output: "html"
      // 可添加宏、字体等其他KaTeX配置
    });

    // 返回成功响应
    console.log(JSON.stringify({ success: true, html: renderedHtml }));
  } catch (error) {
    // 返回错误响应
    console.log(JSON.stringify({ success: false, error: error.message }));
  }
});

2. Ruby Jekyll 插件(_plugins/katex.rb)

这个插件会启动一个长期运行的Node.js子进程,通过管道发送渲染请求并接收结果,同时用单例模式确保整个构建过程只启动一次Node进程:

require 'json'

module Jekyll
  module Tags
    class KatexBlock < Liquid::Block
      # 单例管理Node子进程,避免重复启动的开销
      def self.node_process
        @node_process ||= begin
          # 定位Node脚本路径(根据你的实际目录结构调整)
          script_path = File.expand_path('../../scripts/katex_renderer.js', __FILE__)
          # 启动Node进程,开启双向管道通信
          process = IO.popen(["node", script_path], 'r+')
          # 设置UTF-8编码,避免特殊字符乱码
          process.set_encoding(Encoding::UTF_8)
          process
        end
      end

      def initialize(tag, markup, tokens)
        super
        # 解析标记中的display参数,决定是否用块级渲染
        @display_mode = markup.strip.include?('display')
      end

      def render(context)
        # 获取Block内的LaTeX公式内容
        formula = super(context).strip
        return formula if formula.empty?

        # 构造JSON请求
        request = { formula: formula, displayMode: @display_mode }.to_json
        # 发送请求到Node进程
        self.class.node_process.puts(request)
        self.class.node_process.flush # 确保数据立即发送

        # 读取Node进程的响应
        response_line = self.class.node_process.gets
        return formula unless response_line

        # 解析响应并返回结果
        response = JSON.parse(response_line)
        if response['success']
          response['html']
        else
          # 渲染失败时返回带错误提示的公式
          "<span class='katex-render-error' style='color: #dc2626;'>#{formula} (渲染错误: #{response['error']})</span>"
        end
      end

      # 构建完成后关闭Node进程,避免残留进程
      Jekyll::Hooks.register :site, :post_write do |_site|
        if defined?(@node_process) && @node_process
          @node_process.close
        end
      end
    end
  end
end

Liquid::Template.register_tag('latex', Jekyll::Tags::KatexBlock)

方案优势

  1. 性能提升:进程管道通信比文件读写快得多,且整个构建只启动一次Node进程,避免重复启动的开销
  2. 优雅性:完全脱离磁盘IO,用标准化的进程通信方式实现跨语言交互
  3. 可维护性:JSON格式的请求/响应清晰易懂,便于扩展(比如添加更多KaTeX配置参数)
  4. 错误处理:完善的错误捕获机制,渲染失败时不会中断构建,而是返回带提示的内容

使用注意事项

  • 确保katex.min.js和Node脚本在同一目录,或调整require路径匹配你的文件结构
  • 可以根据需求修改KaTeX的配置(比如开启throwOnError、自定义宏等)
  • Jekyll默认是单线程构建,若使用多线程构建,需调整单例逻辑保证进程安全

内容的提问来源于stack exchange,提问作者Jan Černý

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 21:17:28