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

如何在构建时将TSX编译为模板字面量?(纯SSR Node.js+TS项目)

问题描述

我正在开发一个基于TypeScript的纯SSR Node.js项目,当前用nanohtml库通过模板字面量实现模板引擎,代码示例如下:

// Some .ts file 
import html from 'nanohtml'
export const template = (variable) => html`<p>Hello ${variable}</p>`

// Somewhere (in a .ts file)
res.send( template("world").toString() )

我希望减少生产环境依赖数量,更高效利用已有的TypeScript工具,想知道能不能用类JSX语法,将其编译为JS文件与模板字面量(弃用nanohtml),以及对应的tsconfig.json配置方案,期望实现的代码效果如下:

// Example - Some .tsx file with NO nanohtml lib
export const template = (variable) => <p>Hello ${variable}</p>       // option 1
export const template = (variable) => jsx`<p>Hello ${variable}</p>`  // or option 2
解决方案

完全可以通过TypeScript的JSX编译配置实现这个需求,无需依赖nanohtml,以下是具体实现步骤:

1. 配置tsconfig.json

调整TypeScript的JSX编译选项,让它把JSX转换为自定义逻辑或模板字面量:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "./src/jsx-runtime",
    "target": "ES2020",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

2. 实现Option 1:直接使用语法

在src/jsx-runtime.ts中编写自定义JSX解析逻辑,将JSX元素转为HTML字符串:

// src/jsx-runtime.ts
export type JSXElement = string;

// 转义用户输入,防止XSS
const escapeHtml = (str: string): string => {
  return str.replace(/[&<>"']/g, char => {
    const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
    return map[char as keyof typeof map];
  });
};

export function jsx(tag: string, props: Record<string, any>, ...children: (string | number)[]): string {
  // 处理HTML属性
  const attrs = Object.entries(props || {})
    .map(([key, value]) => {
      if (typeof value === 'boolean') return value ? key : '';
      return `${key}="${escapeHtml(String(value))}"`;
    })
    .filter(Boolean)
    .join(' ');

  const attrsStr = attrs ? ` ${attrs}` : '';
  const childrenStr = children.map(c => escapeHtml(String(c))).join('');

  // 处理自闭合标签
  const selfClosingTags = new Set(['img', 'br', 'input', 'meta', 'link']);
  if (selfClosingTags.has(tag)) {
    return `<${tag}${attrsStr} />`;
  }

  return `<${tag}${attrsStr}>${childrenStr}</${tag}>`;
}

// 兼容多子元素的jsxs,直接复用jsx逻辑
export const jsxs = jsx;

之后在TSX文件中即可直接使用JSX语法:

// src/template.tsx
export const template = (variable: string) => <p>Hello {variable}</p>;

// 路由中使用
res.send(template("world"));

3. 实现Option 2:jsx模板字面量函数

如果更偏好模板字面量写法,无需配置JSX运行时,直接实现一个包装函数即可:

// src/jsx.ts
const escapeHtml = (str: string): string => {
  return str.replace(/[&<>"']/g, char => {
    const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
    return map[char as keyof typeof map];
  });
};

export function jsx(strings: TemplateStringsArray, ...values: any[]): string {
  return strings.reduce((result, str, index) => {
    const value = values[index] ?? '';
    return result + str + escapeHtml(String(value));
  }, '');
}

在文件中导入后使用:

// src/template.tsx
import { jsx } from './jsx';

export const template = (variable: string) => jsx`<p>Hello ${variable}</p>`;

// 使用方式不变
res.send(template("world"));

内容的提问来源于stack exchange,提问作者charnould

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 20:47:35