@cloudpss/template
Version:
String and object template engine for Node.js and the browser.
170 lines • 6.37 kB
JavaScript
import { parseTemplate } from '../parser.js';
/** 是否为 ArrayBuffer */
function isArrayBuffer(value) {
if (value instanceof ArrayBuffer)
return true;
if (typeof SharedArrayBuffer == 'function' && value instanceof SharedArrayBuffer)
return true;
return false;
}
/** 是否为 Error */
function isError(value) {
return value instanceof Error || (typeof DOMException == 'function' && value instanceof DOMException);
}
const KNOWN_ERRORS = [EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError];
// eslint-disable-next-line @typescript-eslint/unbound-method
const toString = Function.call.bind(Object.prototype.toString);
const { hasOwn, defineProperty } = Object;
/** 模板序列号 */
let seq = 0;
/** 创建模板 */
export class TemplateCompiler {
template;
options;
constructor(template, options) {
this.template = template;
this.options = options;
}
params = new Map();
copyable = [];
/** 构建求值 */
buildEval(expression, type) {
const { evaluator } = this.options;
if (!this.params.has('evaluator')) {
this.params.set('evaluator', evaluator.inject);
}
return evaluator.compile(expression, type);
}
/** 构建字符串 */
buildString(str) {
const parsed = parseTemplate(str);
if (typeof parsed === 'string')
return [JSON.stringify(parsed), false];
if (parsed.type === 'formula')
return [this.buildEval(parsed.value, parsed.type), true];
let result = '';
for (let i = 0; i < parsed.templates.length; i++) {
if (parsed.templates[i]) {
result += (result ? '+' : '') + JSON.stringify(parsed.templates[i]);
}
if (i < parsed.values.length) {
if (!result)
result = '""';
result += '+' + this.buildEval(parsed.values[i], parsed.type);
}
}
return [result, true];
}
/** 构建 Error */
buildError(err) {
if (typeof DOMException == 'function' && err instanceof DOMException) {
return `new DOMException(${this.buildString(err.message)[0]}, ${this.buildString(err.name)[0]})`;
}
const constructor = KNOWN_ERRORS.find((type) => err instanceof type)?.name ?? 'Error';
if (err.name === constructor) {
return `new ${constructor}(${this.buildString(err.message)[0]})`;
}
return `Object.assign(new ${constructor}(${this.buildString(err.message)[0]}), {name: ${this.buildString(err.name)[0]}})`;
}
/** 构建数组 */
buildArray(arr) {
return `[${arr.map(this.buildValue.bind(this)).join(',\n')}]`;
}
/** 构建 ArrayBuffer */
buildArrayBuffer(buffer) {
this.copyable.push(buffer.slice(0));
return `copyable[${this.copyable.length - 1}].slice(0)`;
}
/** 构建 ArrayBufferView */
buildArrayBufferView(view) {
this.copyable.push(view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength));
return `new ${view.constructor.name}(copyable[${this.copyable.length - 1}].slice(0))`;
}
/** 构建对象 */
buildObject(obj) {
let result = '';
for (const key in obj) {
if (!hasOwn(obj, key))
continue;
const value = obj[key];
if (result)
result += ',\n';
if (this.options.objectKeyMode === 'ignore') {
result += JSON.stringify(key);
}
else {
const [e, isExpression] = this.buildString(key);
if (isExpression) {
result += '[' + e + ']';
}
else {
result += e;
}
}
result += ':';
result += this.buildValue(value);
}
return '{' + result + '}';
}
/** 构建值 */
buildValue(value) {
if (value === null)
return 'null';
if (value === undefined)
return 'undefined';
if (value === true)
return 'true';
if (value === false)
return 'false';
if (typeof value == 'function')
return 'undefined';
if (typeof value == 'symbol')
return 'undefined';
if (typeof value == 'bigint')
return `${value}n`;
if (typeof value == 'number')
return String(value);
if (typeof value == 'string')
return this.buildString(value)[0];
/* c8 ignore next */
if (typeof value != 'object')
throw new Error(`Unsupported value: ${toString(value)}`);
if (value instanceof Date)
return `new Date(${value.getTime()})`;
if (value instanceof RegExp)
return value.toString();
if (isError(value))
return this.buildError(value);
if (Array.isArray(value))
return this.buildArray(value);
if (isArrayBuffer(value))
return this.buildArrayBuffer(value);
if (ArrayBuffer.isView(value))
return this.buildArrayBufferView(value);
return this.buildObject(value);
}
/** 构建模板 */
build() {
const source = this.buildValue(this.template);
if (this.copyable.length) {
this.params.set('copyable', this.copyable);
}
const params = [...this.params];
try {
// eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
const result = new Function(...params.map(([key]) => key), [
`//# sourceURL=cloudpss-template[${seq++}]`, // sourceURL 用于调试
this.options.evaluator.async
? `return async (context = {}) => (${source});`
: `return (context = {}) => (${source});`,
].join('\n'))(...params.map(([, value]) => value));
defineProperty(result, 'source', { value: source, configurable: true });
return result;
/* c8 ignore next 3 */
}
catch (e) {
throw new Error(`Failed to compile template: ${source}\n${e.message}`, { cause: e });
}
}
}
//# sourceMappingURL=compiler.js.map