MIT License

技术解读

以下内容由 GitRead 中文社区 编辑团队基于项目 README 与官方资料整理, 结合中文开发者的常见使用场景补充技术选型建议。具体以项目官方文档为准。

1 KB 的模板引擎还能预编译:拆解 template.js 的设计取舍

项目背景与痛点

模板引擎的黄金年代似乎已经过去,但“把数据填进一段字符串”的需求从未消失:静态站点生成、Node 服务端渲染 HTML 片段、邮件模板、无框架的 Web Component、SDK 里内嵌的少量视图——这些场景既不需要 Vue 的响应式,也不需要 React 的调度,只需要一个可靠的字符串渲染器。

现成的方案各有代价。Handlebars 光运行时就有几十 KB;art-template 功能齐全,体积同样不轻;EJS 语法友好,但工程化链路偏向服务端;doT 极小,却几乎停止了迭代。更普遍的痛点是模板引擎与构建链路割裂:预编译通常要额外的 CLI 或手写脚本,与现代 bundler 的集成并不顺手。

yanhaijing/template.js 的策略很清晰:把引擎压到几乎可以忽略的体积,同时把预编译能力做成构建工具的一等公民。README 里那一长串 webpack / rspack / vite / rollup / esbuild / parcel / browserify / fis / gulp,从最新到几乎绝迹的工具一家不落——这说明作者真正瞄准的用户是维护存量工程的人,而不是只写新项目的开发者。

核心技术揭秘

template.js 在结构上分为“编译器”和“运行时”两半,这种分离是它体积小的根本原因。

编译器做的是典型的 scan → codegen 两段式工作:用正则扫描模板中的 <%= %>(转义插值)、<%- %>(原样插值)、<% %>(逻辑语句)这几类标记,把模板切成静态文本块与代码块组成的 chunk 序列。随后拼接函数体源码:静态文本直接变成字符串字面量输出,代码块原样嵌入函数逻辑。最终产物是一个可执行函数的源码字符串,交由 new Function 构造出渲染函数。

这里的核心取舍在数据作用域。模板里可以直接写 title 而不用写 data.title,靠的是让数据对象成为函数执行上下文的一部分。这种实现让模板语法极其自然,代价是它触碰了严格模式下禁用 with 这一限制,V8 也很难对这类动态作用域做优化。若换成参数解构方案,语义会更干净,但模板写起来会更啰嗦——template.js 选择了偏重易用性的一侧。

运行时则尽量精简:核心是 escape 实现和少量工具函数,<% 逻辑由编译产物自己承担。模板字符串到编译函数的映射会被缓存,因此热路径上重复 render 同一模板时不会重复编译。

预编译是它最有价值的工程能力:把编译器产出的函数源码交给对应 bundler 的 loader/plugin,在构建期写进 bundle,运行时只做“还原并执行”,跳过扫描与 codegen。

graph TD A[模板字符串] --> B[正则扫描标记块] B --> C[切分为静态文本与代码 chunk] C --> D{"构建期是否预编译"} D -->|"是"| E[loader 输出函数源码模块] D -->|"否"| F[运行时编译] E --> G[bundle 内置渲染函数] F --> H[拼接函数体源码] H --> I[new Function 构造渲染函数] I --> J[按模板字符串缓存复用] G --> K[注入数据执行] J --> K K --> L[输出 HTML 字符串]

功能亮点与差异

体积是第一杀手锏:压缩后的运行时在 1 KB 量级,这意味着你可以把它塞进任何一个库而无须解释“为什么多了 30 KB 依赖”。

构建工具覆盖度是第二亮点,且罕见。多数同类项目只维护 webpack 与 vite 两个插件,template.js 连 fis、gulp、browserify 这类处于后期的工具都没放弃,对老项目的迁移非常友好。

第三是语法亲和力。它与 EJS 高度相似,<%= %> 的直觉几乎不需要学习成本,存量 EJS 模板改造成本接近于零。

差异化的地方也很明确:相比 art-template,它没有过滤器管道、模板继承、子模板 include 的完整体系;相比 EJS,它的预编译链路更顺、体积更小。它有意只做一个“最小可用集”。

应用场景与落地建议

适合:静态站点/SSG 的片段渲染、Node 侧轻量 HTML 拼装、邮件模板、无框架页面与 Web Component、需要在 SDK 内嵌极小视图库的场景。

落地时需要注意以下几点:

  1. CSP 是最大隐患。new Function 依赖 unsafe-eval,在浏览器扩展、金融后台、启用严格 CSP 的页面中会直接失效。这类环境必须走 loader 预编译路径,并确认产物是可直接执行的模块,而不是运行时仍需 eval 的字符串——这是最容易出错的地方。
  2. XSS 边界要划清。默认用 <%= %>,只有确认内容本身就是可信 HTML 时才使用 <%- %>。
  3. 不要在模板里写业务逻辑。长段 JS 塞进 <% %> 会让模板退化成难以维护的“字符串里的程序”。
  4. 确保命中缓存。在热路径上提前编译好函数并复用,不要每次 render 都传模板字符串。

综合评价

优点很突出:把“极小体积”和“能接入现代/存量构建链”两件事同时做到了,API 克制、零依赖、学习成本几乎为零,是性价比极高的选择。

短板同样清晰:单维护者项目,功能面窄,缺少布局继承与过滤器这类体系化能力;错误信息与源码映射不会像成熟框架那样精细;严格 CSP 场景需要额外工程处理;with 的历史包袱也限制了它在某些模块环境下的直接使用。

一句话总结:如果你要的是“能预编译、能进 bundle、体积可以忽略”的模板能力,它几乎是最优解;如果你要的是一整套模板体系(布局、过滤器、国际化插桩),请直接去看 art-template 或 EJS。

关于

GitHub 原始描述(英文)

A javascript template engine, simple, easy & extras, support webpack, rspack, vite, rollup, esbuild, parcel, browserify, fis and gulp.

描述取自 GitHub 官方 API,保持仓库作者书写的原文。

简要分析

热度分
2335
价值分
941
活跃状态
活跃

语言占比

CSS
HTML
JavaScript
Shell
TypeScript

README

template.js

license CI

English | 简体中文

A javascript template engine that is simple, easy to use, and supports webpack, rspack, vite, esbuild, rollup, parcel, browserify, fis, and gulp.

Features

  • Native JS syntax, template parsing, compilation, and rendering
  • Supports all browsers and Node, supports TypeScript
  • Precompilation supports mainstream packaging tools
  • Custom configurations, decorators, and functions
  • Data filtering support
  • Exception capture functionality
  • Subtemplates
  • Sandbox mode
  • Supports JIT and AOT compilation modes
  • Provides CLI integrated tools
  • Provides support for editor plugins

User Guide

template.js is a better way than concatenating strings, Click to use codesandbox for a quick demo

Template example

<ul>
    <%for(var i = 0; i < list.length; i++) {%>
        <li><%:=list[i].name%></li>
    <%}%>
</ul>

Data example

const data = {
  list: [{ name: 'yan' }, { name: 'haijing' }],
};

Render output

<ul>
  <li>yan</li>
  <li>haijing</li>
</ul>

Editor plugins supported by template.js

Editor Plugin
Vscode highlight plugin
Sublime highlight plugin
Atom highlight plugin
WebStorm TODO

Quickly initialize a project using CLI tools

$ npx @templatejs/cli new myapp
# Choose the corresponding platform
# ❯ webpack
#   rspack
#   vite
#   rollup
#   esbuild
#   parcel2
#   parcel1
#   fis3
#   browserify
#   gulp
#   browser

If you already have a project, you can choose a corresponding plugin, template.js supports different usage methods

Platform Plugin
webpack / rspack template-loader
vite / rollup rollup-plugin-templatejs
esbuild esbuild-plugin-templatejs
parcel2 parcel-transformer-template
parcel1 parcel-plugin-template
fis fis-parser-template
browserify browserify-templatejs
gulp gulp-templatejs
Native web & Node.js template_js
Unsupported platforms You can write a plugin yourself, please see @templatejs/precompiler

Other packages summary

Developer Guide

This project uses lerna to manage multiple plugins. Common lerna commands are as follows, note that the npx prefix cannot be left out:

$ npx lerna init # Initialize
$ npx lerna create @templatejs/parser # Create a package
$ npx lerna add yargs --scope=@templatejs/parser # Install package dependencies
$ npx lerna list # List all packages
$ npx lerna bootstrap # Install all dependencies
$ npx lerna link # Create all soft links
$ npx lerna changed # List the packages to be updated next time when using lerna publish
$ npx lerna publish # Will tag, upload git, upload npm

Release steps, modify changelog

$ yarn lint
$ yarn build
$ yarn test
$ npx lerna publish

Release without tagging, suitable for test package.

$ yarn lint
$ yarn build
$ yarn test
$ lerna version --no-git-tag-version
$ npx lerna publish from-package --dist-tag next

Contributors List

contributors

Changelog

CHANGELOG.md

Planned Feature List

TODO.md

Who is Using

To learn about who is using this, click here.

评论

暂无评论