2.0.0
parent
f77109be52
commit
6b165f7aba
6
index.js
6
index.js
|
@ -1,10 +1,8 @@
|
||||||
/**
|
/**
|
||||||
* nodeJS 模板引擎
|
* nodeJS 模板引擎
|
||||||
* @authors yutent (yutent@doui.cc)
|
* @author yutent<yutent.io@gmail.com>
|
||||||
* @date 2015-12-28 13:57:12
|
* @date 2020/09/18 13:36:47
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
'use strict'
|
|
||||||
|
|
||||||
require('es.shim')
|
require('es.shim')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* nodeJS 模板引擎
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2020/09/18 13:36:47
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'es.shim'
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'iofs'
|
||||||
|
|
||||||
|
import Tool from './lib/tool.mjs'
|
||||||
|
|
||||||
|
function hash(str) {
|
||||||
|
return Buffer.from(str).toString('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Smarty {
|
||||||
|
constructor(opt) {
|
||||||
|
this.opt = { cache: true, ext: '.htm' }
|
||||||
|
if (opt) {
|
||||||
|
Object.assign(this.opt, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__REG__ = new RegExp(this.opt.ext + '$')
|
||||||
|
this.tool = new Tool(this.opt)
|
||||||
|
this.__DATA__ = Object.create(null) // 预定义的变量储存
|
||||||
|
this.__CACHE__ = Object.create(null) // 渲染缓存
|
||||||
|
}
|
||||||
|
|
||||||
|
config(key, val) {
|
||||||
|
key += ''
|
||||||
|
if (!key || val === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.opt[key] = val
|
||||||
|
this.tool.opt[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义变量
|
||||||
|
* @param {Str} key 变量名
|
||||||
|
* @param {any} val 值
|
||||||
|
*/
|
||||||
|
assign(key, val) {
|
||||||
|
key += ''
|
||||||
|
if (!key) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
this.__DATA__[key] = val
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [render 模板渲染]
|
||||||
|
* @param {String} filePath 模板路径
|
||||||
|
* @param {Boolean} noParse 不解析直接读取
|
||||||
|
* @return {Promise} 返回一个Promise对象
|
||||||
|
*/
|
||||||
|
render(filePath = '', noParse = false) {
|
||||||
|
var key = null
|
||||||
|
var cache
|
||||||
|
if (!this.opt.path) {
|
||||||
|
throw new Error('Smarty engine must define path option')
|
||||||
|
}
|
||||||
|
if (!filePath) {
|
||||||
|
return Promise.reject('argument[filePath] can not be empty')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.__REG__.test(filePath)) {
|
||||||
|
filePath += this.opt.ext
|
||||||
|
}
|
||||||
|
filePath = path.resolve(this.opt.path, filePath)
|
||||||
|
|
||||||
|
key = hash(filePath)
|
||||||
|
|
||||||
|
if (this.__CACHE__[key]) {
|
||||||
|
return Promise.resolve(fs.cat(path.resolve('./cache/', key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
cache = this.tool.__readFile__(filePath, noParse)
|
||||||
|
|
||||||
|
if (noParse) {
|
||||||
|
this.__CACHE__[key] = true
|
||||||
|
fs.echo(cache, path.resolve('./cache/', key))
|
||||||
|
return Promise.resolve(cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cache = this.tool.parse(cache, this.__DATA__)
|
||||||
|
if (this.opt.cache) {
|
||||||
|
this.__CACHE__[key] = true
|
||||||
|
fs.echo(cache, path.resolve('./cache/', key))
|
||||||
|
}
|
||||||
|
return Promise.resolve(cache)
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* 模板引擎预处理
|
* 模板引擎预处理
|
||||||
* @authors yutent (yutent@doui.cc)
|
* @author yutent<yutent.io@gmail.com>
|
||||||
* @date 2016-01-02 21:26:49
|
* @date 2020/09/18 13:46:19
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const fs = require('iofs')
|
const fs = require('iofs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,334 @@
|
||||||
|
/**
|
||||||
|
* 模板引擎预处理
|
||||||
|
* @author yutent<yutent.io@gmail.com>
|
||||||
|
* @date 2020/09/18 13:46:19
|
||||||
|
*/
|
||||||
|
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'iofs'
|
||||||
|
|
||||||
|
export default class Tool {
|
||||||
|
constructor(opt) {
|
||||||
|
this.opt = {
|
||||||
|
delimiter: ['<!--{', '}-->'], //模板界定符
|
||||||
|
labels: {
|
||||||
|
//支持的标签类型
|
||||||
|
extends: 'extends ([^\\{\\}\\(\\)]*?)', //引入其他文件
|
||||||
|
inc: 'include ([^\\{\\}\\(\\)]*?)', //引入其他文件
|
||||||
|
each: 'each ([^\\{\\}\\(\\)]*?)', //each循环开始
|
||||||
|
done: '/each', //each循环结束
|
||||||
|
blockL: 'block ([^\\{\\}\\(\\)]*?)', //each循环开始
|
||||||
|
blockR: '/block', //each循环结束
|
||||||
|
if: 'if ([^\\{\\}\\/]*?)', //if开始
|
||||||
|
elif: 'elseif ([^\\{\\}\\/]*?)', //elseif开始
|
||||||
|
else: 'else', //else开始
|
||||||
|
fi: '/if', //if结束
|
||||||
|
var: 'var ([\\s\\S]*?)', //定义变量
|
||||||
|
echo: '=([^\\{\\}]*?)', //普通变量
|
||||||
|
comment: '#([\\s\\S]*?)#' //引入其他文件
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this.opt, opt)
|
||||||
|
|
||||||
|
this.__REG__ = new RegExp(this.opt.ext + '$')
|
||||||
|
|
||||||
|
//过滤器
|
||||||
|
this.filters = {
|
||||||
|
html: function(str = '') {
|
||||||
|
str += ''
|
||||||
|
return str.tohtml()
|
||||||
|
},
|
||||||
|
truncate: function(str, len = '', truncation = '...') {
|
||||||
|
str += ''
|
||||||
|
//防止模板里参数加了引号导致异常
|
||||||
|
len = len.replace(/['"]/g, '') - 0
|
||||||
|
if (str.length <= len || len < 1) return str
|
||||||
|
|
||||||
|
//去除参数里多余的引号
|
||||||
|
truncation = truncation.replace(/^['"]/, '').replace(/['"]$/, '')
|
||||||
|
|
||||||
|
return str.slice(0, len) + truncation
|
||||||
|
},
|
||||||
|
lower: function(str) {
|
||||||
|
str += ''
|
||||||
|
return str.toLowerCase()
|
||||||
|
},
|
||||||
|
upper: function(str) {
|
||||||
|
str += ''
|
||||||
|
return str.toUpperCase()
|
||||||
|
},
|
||||||
|
date: function(str, format = '') {
|
||||||
|
//去除参数里多余的引号
|
||||||
|
format = format.replace(/^['"]/, '').replace(/['"]$/, '')
|
||||||
|
if (isFinite(str)) {
|
||||||
|
str = +str
|
||||||
|
}
|
||||||
|
return new Date(str).format(format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__readFile__(file, noParse) {
|
||||||
|
var buf = null
|
||||||
|
if (!fs.exists(file)) {
|
||||||
|
throw new Error(`Can not find template "${file}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = fs.cat(file).toString()
|
||||||
|
|
||||||
|
if (noParse) {
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
.replace(/[\r\n\t]+/g, ' ') //去掉所有的换行/制表
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成正则
|
||||||
|
__exp__(str) {
|
||||||
|
return new RegExp(str, 'g')
|
||||||
|
}
|
||||||
|
|
||||||
|
//生成模板标签
|
||||||
|
__label__(id) {
|
||||||
|
var opt = this.opt
|
||||||
|
var tag = opt.labels[id]
|
||||||
|
return this.__exp__(opt.delimiter[0] + tag + opt.delimiter[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析普通字段
|
||||||
|
matchNormal(m) {
|
||||||
|
let begin = this.__exp__('^' + this.opt.delimiter[0] + '[=\\s]?')
|
||||||
|
let end = this.__exp__(this.opt.delimiter[1] + '$')
|
||||||
|
|
||||||
|
m = m
|
||||||
|
.replace(begin, '')
|
||||||
|
.replace(end, '')
|
||||||
|
.replace(/\|\|/g, '\t')
|
||||||
|
|
||||||
|
let matches = m.split('|')
|
||||||
|
let filter = matches.length == 1 ? '' : matches[1].trim()
|
||||||
|
let txt = matches[0].replace(/\t/g, '||').trim()
|
||||||
|
|
||||||
|
// 默认过滤HTML标签
|
||||||
|
txt = txt.htmlspecialchars()
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
let args = filter.split(':')
|
||||||
|
filter = args.splice(0, 1, txt) + ''
|
||||||
|
if (filter === 'date' && args.length > 2) {
|
||||||
|
let tmp = args.splice(0, 1)
|
||||||
|
tmp.push(args.join(':'))
|
||||||
|
args = tmp
|
||||||
|
tmp = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filters.hasOwnProperty(filter)) {
|
||||||
|
args = args.map((it, i) => {
|
||||||
|
if (i === 0) {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
return `'${it}'`
|
||||||
|
})
|
||||||
|
txt = `__filters__.${filter}(${args.join(', ')})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `\` + (${txt}); tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析each循环
|
||||||
|
matchFor(m) {
|
||||||
|
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'each\\s+')
|
||||||
|
let end = this.__exp__(this.opt.delimiter[1] + '$')
|
||||||
|
|
||||||
|
m = m.replace(begin, '').replace(end, '')
|
||||||
|
|
||||||
|
m = m.trim()
|
||||||
|
if (!m || !/\sin\s/.test(m)) {
|
||||||
|
return new Error('Wrong each loop')
|
||||||
|
}
|
||||||
|
|
||||||
|
let each = 'for (let '
|
||||||
|
let ms = m.split(' in ')
|
||||||
|
let mi = ms[0].trim().split(' ')
|
||||||
|
let mf = ms[1].trim() //要遍历的对象
|
||||||
|
|
||||||
|
if (mi.length === 1) {
|
||||||
|
each += `d_idx in ${mf}) { let ${mi[0]} = ${mf}[d_idx]; tpl += \``
|
||||||
|
} else {
|
||||||
|
each += `${mi[0]} in ${mf}) { let ${mi[1]} = ${mf}[${mi[0]}]; tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
return `\`; ${each}`
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析条件语句
|
||||||
|
matchIf(m) {
|
||||||
|
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'if\\s+')
|
||||||
|
let end = this.__exp__(this.opt.delimiter[1] + '$')
|
||||||
|
|
||||||
|
m = m.replace(begin, '').replace(end, '')
|
||||||
|
|
||||||
|
m = m.trim()
|
||||||
|
if (!m) {
|
||||||
|
return `\`; tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
return `\`; if (${m}){ tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析条件语句
|
||||||
|
matchElseIf(m) {
|
||||||
|
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'elseif\\s+')
|
||||||
|
let end = this.__exp__(this.opt.delimiter[1] + '$')
|
||||||
|
|
||||||
|
m = m.replace(begin, '').replace(end, '')
|
||||||
|
|
||||||
|
m = m.trim()
|
||||||
|
if (!m) {
|
||||||
|
return `\`;} else { tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
return `\`; } else if (${m}){ tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析变量定义
|
||||||
|
matchVar(m) {
|
||||||
|
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'var\\s+')
|
||||||
|
let end = this.__exp__(this.opt.delimiter[1] + '$')
|
||||||
|
|
||||||
|
m = m.replace(begin, '').replace(end, '')
|
||||||
|
|
||||||
|
m = m.trim()
|
||||||
|
if (m && /=/.test(m)) {
|
||||||
|
m = 'let ' + m
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vars += ` ${m};`
|
||||||
|
|
||||||
|
return `\`; tpl += \``
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析include
|
||||||
|
matchInclude(m) {
|
||||||
|
let begin = this.__exp__('^' + this.opt.delimiter[0] + 'include\\s+')
|
||||||
|
let end = this.__exp__(this.opt.delimiter[1] + '$')
|
||||||
|
var tpl = ''
|
||||||
|
|
||||||
|
m = m
|
||||||
|
.replace(begin, '')
|
||||||
|
.replace(end, '')
|
||||||
|
.replace(/^['"]/, '')
|
||||||
|
.replace(/['"]$/, '')
|
||||||
|
.replace(this.__REG__, '') //去掉可能出现的自带的模板后缀
|
||||||
|
|
||||||
|
m += this.opt.ext //统一加上后缀
|
||||||
|
|
||||||
|
tpl = this.__readFile__(path.resolve(this.opt.path, m))
|
||||||
|
//递归解析include
|
||||||
|
tpl = tpl.replace(this.__label__('inc'), m1 => {
|
||||||
|
return this.matchInclude(m1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return tpl
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析常规标签
|
||||||
|
parseNormal(str) {
|
||||||
|
return (
|
||||||
|
str
|
||||||
|
// 解析include
|
||||||
|
.replace(this.__label__('inc'), m => {
|
||||||
|
return this.matchInclude(m)
|
||||||
|
})
|
||||||
|
// 移除注释
|
||||||
|
.replace(this.__label__('comment'), m => {
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
// 解析each循环
|
||||||
|
.replace(this.__label__('each'), m => {
|
||||||
|
return this.matchFor(m)
|
||||||
|
})
|
||||||
|
// 解析循环结束标识
|
||||||
|
.replace(this.__label__('done'), '` } tpl += `')
|
||||||
|
// 解析 if/elseif 条件
|
||||||
|
.replace(this.__label__('if'), m => {
|
||||||
|
return this.matchIf(m)
|
||||||
|
})
|
||||||
|
.replace(this.__label__('elif'), m => {
|
||||||
|
return this.matchElseIf(m)
|
||||||
|
})
|
||||||
|
// 解析else
|
||||||
|
.replace(this.__label__('else'), '`; } else { tpl += `')
|
||||||
|
// 解析if条件结束标识
|
||||||
|
.replace(this.__label__('fi'), '`; } tpl += `')
|
||||||
|
// 解析临时变量的定义
|
||||||
|
.replace(this.__label__('var'), m => {
|
||||||
|
return this.matchVar(m)
|
||||||
|
})
|
||||||
|
// 解析普通变量/字段
|
||||||
|
.replace(this.__label__('echo'), m => {
|
||||||
|
return this.matchNormal(m)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析extends标签
|
||||||
|
parseExtends(str) {
|
||||||
|
let matches = str.match(/^<!--{extends ([^\\{\\}\\(\\)]*?)\s*?}-->/)
|
||||||
|
if (!matches) {
|
||||||
|
str = str
|
||||||
|
.replace(this.__label__('blockL'), '')
|
||||||
|
.replace(this.__label__('blockR'), '')
|
||||||
|
} else {
|
||||||
|
let blocks = {}
|
||||||
|
// 去除所有的extends标签, 只允许有出现1次
|
||||||
|
str = str.replace(this.__label__('extends'), '').trim()
|
||||||
|
str.replace(
|
||||||
|
/<!--{block ([^\\{\\}\\(\\)]*?)}-->([\s\S]*?)<!--{\/block}-->/g,
|
||||||
|
(m, flag, val) => {
|
||||||
|
flag = flag.trim()
|
||||||
|
blocks[flag] = val.trim()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
str = matches[1]
|
||||||
|
.replace(/^['"]/, '')
|
||||||
|
.replace(/['"]$/, '')
|
||||||
|
.replace(this.__REG__, '') //去掉可能出现的自带的模板后缀
|
||||||
|
|
||||||
|
str += this.opt.ext //统一加上后缀
|
||||||
|
|
||||||
|
str = this.__readFile__(path.resolve(this.opt.path, str)).replace(
|
||||||
|
this.__label__('blockL'),
|
||||||
|
(m, flag) => {
|
||||||
|
flag = flag.trim()
|
||||||
|
return blocks[flag] || ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
//解析模板
|
||||||
|
parse(str, data) {
|
||||||
|
var vars = `"use strict"; let __filters__ = f; `
|
||||||
|
for (let i in data) {
|
||||||
|
let tmp = JSON.stringify(data[i]) || ''
|
||||||
|
vars += `let ${i} = ${tmp}; `
|
||||||
|
}
|
||||||
|
str = str
|
||||||
|
.trim()
|
||||||
|
.replace(/[\r\n\t]+/g, ' ') // 去掉所有的换行/制表
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/`/g, '\\`')
|
||||||
|
|
||||||
|
str = this.parseExtends(str)
|
||||||
|
str = this.parseNormal(str)
|
||||||
|
|
||||||
|
str = `${vars} let tpl=\`${str}\`; return tpl;`
|
||||||
|
|
||||||
|
return new Function('f', str)(this.filters)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../../../bytedo/es.shim
|
|
@ -0,0 +1 @@
|
||||||
|
../../../bytedo/iofs
|
19
package.json
19
package.json
|
@ -1,18 +1,21 @@
|
||||||
{
|
{
|
||||||
"name": "smartyx",
|
"name": "smartyx",
|
||||||
"version": "1.4.0",
|
"version": "2.0.0",
|
||||||
"description": "nodeJS模板引擎,理念源自于PHP的smarty模板引擎",
|
"description": "nodeJS模板引擎,理念源自于PHP的smarty模板引擎",
|
||||||
"keywords": ["fivejs", "smarty", "template", "ejs", "jade"],
|
"keywords": ["fivejs", "php", "smarty", "template", "ejs", "jade"],
|
||||||
"author": "宇天 <yutent@doui.cc>",
|
"author": "yutent <yutent.io@gmail.com>",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/yutent/smarty.git"
|
"url": "https://github.com/bytedo/smarty.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es.shim": "^1.1.2",
|
"es.shim": "^2.0.0",
|
||||||
"iofs": "^1.3.2"
|
"iofs": "^2.0.0"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"exports": {
|
||||||
|
"require": "./index.js",
|
||||||
|
"import": "./index.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
|
||||||
"main": "lib/main.js",
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue