如何在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)
方案优势
- 性能提升:进程管道通信比文件读写快得多,且整个构建只启动一次Node进程,避免重复启动的开销
- 优雅性:完全脱离磁盘IO,用标准化的进程通信方式实现跨语言交互
- 可维护性:JSON格式的请求/响应清晰易懂,便于扩展(比如添加更多KaTeX配置参数)
- 错误处理:完善的错误捕获机制,渲染失败时不会中断构建,而是返回带提示的内容
使用注意事项
- 确保
katex.min.js和Node脚本在同一目录,或调整require路径匹配你的文件结构 - 可以根据需求修改KaTeX的配置(比如开启
throwOnError、自定义宏等) - Jekyll默认是单线程构建,若使用多线程构建,需调整单例逻辑保证进程安全
内容的提问来源于stack exchange,提问作者Jan Černý
相关产品推荐
相关产品推荐

