tmod-loader
Version:
artTemplate loader module for webpack
234 lines (165 loc) • 5.79 kB
JavaScript
/*!
* TmodJS - AOT Template Compiler
* https://github.com/aui/tmodjs
* Released under the MIT, BSD, and GPL Licenses
*/
;
module.exports = function (template) {
/**
* 模板预编译器,根据设置生成不同格式的 javascript 模块
* @param {String} 模板源代码
* @param {Object} 选项
*/
template.AOTcompile = function (source, options) {
var uri = './' + options.filename;
// 是否为编译为调试版本
var debug = options.debug;
// 模板名(不能以 . 或者 / 开头)
var filename = options.filename;
// 编译的模块类型
var type = options.type;
// 运行时模块别名。设置此后 runtime 的路径将被写死
var alias = options.alias;
// 运行时名
var runtime = options.runtime;
// 是否压缩 HTML 多余空白字符
var compress = debug ? true : options.compress;
// 模板文件后缀
var suffix = options.suffix || '';
options.cache = false;
var render = compile(source, options);
var requires = parseDependencies(render);
var isLogic = testTemplateSyntax(source, options);
var dir = dirname(uri);
var code = '';
if (!isLogic) {
code = compress ? compressHTML(source) : source;
code = stringify(code);
} else {
code = render.replace(ANONYMOUS_RE, 'function');
}
// 计算主入口相对于当前模板路径
var getRuntime = function () {
if (alias) {
return alias;
}
var prefix = './';
var length = dir.split('/').length - 2;
if (length) {
prefix = (new Array(length + 1)).join('../');
}
return prefix + runtime.replace(/\.js$/, '');
};
// 生成 require 函数依赖声明代码
var getRequireCode = function () {
var requiresCode = [];
requires.forEach(function (uri) {
requiresCode.push("require('" + uri + options.suffix +"');");
});
return requiresCode.join('\n');
};
code
= "var template=require('" + getRuntime() + "');"
+ getRequireCode()
+ "module.exports=template('" + filename + "'," + code + ");";
return {
// 编译结果
code: code,
// 依赖的子模板
requires: requires.map(function (subUir) {
testUri(subUir, uri, source);
return resolve(dir + subUir);
})
};
};
template.config('cache', false);
template.onerror = function (e) {
throw e;
};
var SLASH_RE = /\\\\/g;
var DOT_RE = /\/\.\//g;
var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
var DIRNAME_RE = /[^/]+$/;
var ANONYMOUS_RE = /^function\s+anonymous/;
var EXTNAME_RE = /\.(html|htm|tpl)$/i;
var INCLUDE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*include|(?:^|[^$])\binclude\s*\(\s*(["'])(.+?)\1/g; //"
// 编译模板
var compile = function (source, options) {
var render = template.compile(source, options);
return render
.toString()
.replace(ANONYMOUS_RE, 'function');
};
// 检查模板是否有逻辑语法
var testTemplateSyntax = function (source, options) {
return source.indexOf(options.openTag) !== -1;
};
// 模板 filename 规范检查
// 保证 include 语法引用的是相对路径
var testUri = function (uri, fromUri, source) {
if (!/^\./.test(uri) || EXTNAME_RE.test(uri)) {
var line;
// 如果只出现一次这个字符串,很容易确认模板错误行
if (source.split(uri).length === 2) {
source.split(/\n/).forEach(function (code, index) {
if (code.indexOf(uri) !== -1) {
line = index + 1;
source = code.trim();
}
});
}
var error = {
name: 'Syntax Error',
line: line,
source: source,
message: 'Template must be a relative path, and can not have a suffix.'
};
if (fromUri) {
error.filename = fromUri;
}
throw error;
}
};
// 依赖分析
var parseDependencies = function (code) {
var list = [];
var uniq = {};
code
.replace(SLASH_RE, '')
.replace(INCLUDE_RE, function(m, m1, m2) {
if (m2 && !uniq.hasOwnProperty(m2)) {
list.push(m2);
uniq[m2] = true;
}
});
return list;
};
// 获取上一层 uri
var dirname = function (uri) {
return uri.replace(DIRNAME_RE, '');
};
// 分解为标准化 uri
var resolve = function (uri) {
uri = uri.replace(DOT_RE, '/');
while (uri.match(DOUBLE_DOT_RE)) {
uri = uri.replace(DOUBLE_DOT_RE, '/');
}
return uri;
};
// 构造字符串表达式
var stringify = function (code) {
return "'" + code
.replace(/('|\\)/g, '\\$1')
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
+ "'";
};
// 压缩 HTML 字符串
var compressHTML = function (code) {
code = code
.replace(/\s+/g, ' ')
.replace(/<!--[\w\W]*?-->/g, '');
return code;
};
return template;
};