
EJS 官方 CLI 不支持递归处理目录,但可通过 Node.js 脚本轻松实现:遍历源目录所有文件(含子目录),对每个 .ejs 或纯文本文件执行渲染,并按原始路径结构输出到目标目录。本文提供可直接运行的健壮脚本,支持数据注入、路径保留与扩展名智能处理。
ejs 官方 cli 不支持递归处理目录,但可通过 node.js 脚本轻松实现:遍历源目录所有文件(含子目录),对每个 `.ejs` 或纯文本文件执行渲染,并按原始路径结构输出到目标目录。本文提供可直接运行的健壮脚本,支持数据注入、路径保留与扩展名智能处理。
虽然 EJS 官方 CLI(如 ejs template.ejs -o output.html)仅支持单文件渲染,但实际项目中常需将整套含 占位符的模板目录(如网站骨架、配置生成器或代码模板库)批量渲染为静态产物。此时,自研轻量级递归渲染脚本是高效且可控的选择。
以下是一个生产就绪的 Node.js 脚本,具备以下关键特性:
- ✅ 递归遍历输入目录(保留完整子目录结构)
- ✅ 自动识别并渲染
.ejs文件;非.ejs文件(如.json,.md,.js)也可按需渲染(通过配置白名单) - ✅ 支持统一数据源(推荐使用
data.json文件,位于输入目录根部) - ✅ 输出路径严格对应输入路径,避免文件覆盖或错位
- ✅ 自动移除
.ejs后缀(如index.ejs→index),或按需保留(如config.json.ejs→config.json)
// render-ejs-dir.js
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const INPUT_DIR = 'my-template';
const OUTPUT_DIR = 'output';
// 加载全局数据(支持 JSON 或 JS 模块)
const DATA_FILE = path.join(INPUT_DIR, 'data.json');
const data = fs.existsSync(DATA_FILE)
? JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'))
: {};
// 可选:定义需渲染的文件扩展名(默认仅 .ejs;添加 '.md' 或 '*' 表示全部)
const RENDER_EXTENSIONS = new Set(['.ejs']);
function renderFile(filePath, relativePath) {
const content = fs.readFileSync(filePath, 'utf8');
let rendered = ejs.render(content, data, {
filename: filePath, // 提升错误堆栈可读性
});
// 生成输出路径:移除 .ejs 后缀,或按规则重写
const ext = path.extname(filePath);
const basename = path.basename(filePath, ext);
const outBasename = RENDER_EXTENSIONS.has(ext) ? basename : path.basename(filePath);
const outputPath = path.join(OUTPUT_DIR, relativePath.replace(/\.ejs$/, ''));
// 确保输出目录存在
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, rendered);
}
function walkDirectory(dir, relativeBase = '') {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relPath = path.join(relativeBase, entry.name);
if (entry.isDirectory()) {
walkDirectory(fullPath, relPath);
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
// 渲染条件:是 .ejs 文件,或 RENDER_EXTENSIONS 包含该扩展名,或通配符启用
if (RENDER_EXTENSIONS.has(ext) || RENDER_EXTENSIONS.has('*')) {
renderFile(fullPath, relPath);
}
// 非 EJS 文件默认原样复制(如需渲染,取消下方注释并调整逻辑)
// else {
// const outPath = path.join(OUTPUT_DIR, relPath);
// fs.mkdirSync(path.dirname(outPath), { recursive: true });
// fs.copyFileSync(fullPath, outPath);
// }
}
}
}
// 执行入口
try {
fs.rmSync(OUTPUT_DIR, { recursive: true, force: true });
walkDirectory(INPUT_DIR);
console.log(`✅ 渲染完成!输出至 ${path.resolve(OUTPUT_DIR)}`);
} catch (err) {
console.error('❌ 渲染失败:', err.message);
process.exit(1);
}
使用说明:
- 将脚本保存为
render-ejs-dir.js,与my-template/目录同级; - 在
my-template/data.json中定义共享数据(如{ "title": "My Site", "version": "1.2.0" }); - 运行命令:
node render-ejs-dir.js; - 输出将自动创建
output/目录,结构与my-template/一致,.ejs后缀被剥离。
注意事项:
-
安全第一:EJS 默认启用
escapeXML,但若需插入 HTML,请显式使用并确保数据可信; -
路径兼容性:脚本使用
path.join()和fs.rmSync(..., {recursive:true}),要求 Node.js ≥ 14.14; -
性能优化:对于超大目录(>1000 文件),可改用
fs.promises+async/await避免阻塞; -
进阶需求:如需支持多环境数据(
data.dev.json/data.prod.json)、文件过滤或自定义模板函数,可在data对象中注入方法(如data.date = () => new Date().toISOString())。
该方案无需引入额外 CLI 工具或 fork 仓库,零依赖、易维护、可嵌入 CI/CD 流程,是 EJS 项目规模化渲染的事实标准实践。











