aui / art-template

High performance JavaScript templating engine
https://aui.github.io/art-template/
MIT License
9.86k stars 2.68k forks source link

是否支持加载文件,继承 #57

Closed majinkai closed 10 years ago

majinkai commented 10 years ago

类似EJS的文件加载,希望能够以文件片段的方式管理 模板是否支持extends,而非include

aui commented 10 years ago

这个功能正在实现中…… 如果使用预编译工具,文件片段管理已经实现。参见 artTemplate 预编译工具:tmodjs

majinkai commented 10 years ago

我目前已经选定使用artTemplate,因为我们是在客户端使用,所以自己对其做了简单的改造,使用XMLHttpRequest读取模板文件,现在最迫切的需求是模板继承特性,例如: layout.tp

<div>I am in layout.tp</div>
<div><%block('child')%></div>

child.tp

<%extends('layout.tp')%>
<%child%>
//Output
<%end%>

PS:看到你们一直在维护,就放心了

aui commented 10 years ago

artTemplate 定位是一个小巧高效的支持预编译的模板引擎,所以暂时不会支持模板的 extends,只支持最基础的 include 功能(可以定义传入的数据)。 另外,使用 XMLHttpRequest 引入模板是否过于复杂?采用同步模式加载文件?

majinkai commented 10 years ago

没错,是同步模式加载文件,主要是我们的工程非常大(不是single page),考虑到模板文件重用,并且想让前后端完全分离,后端暴露api,前端只负责数据展现和简单处理,如果没有include特性,可能前端每个html都需要引入很多公共模板,比如导航,footer这些。 include其实也够用了,只不过会有很多重复声明include的代码。

aui commented 10 years ago

artTemplate 不提供模板管理,新的版本计划通过给外部暴露接口来实现文件级加载:

提供template.loadTemplate(id)方法来加载模板。引擎默认在浏览器中使用,内部使用document.getElementById(id)方式获取页面内嵌的模板。

加载外部模板可以复写这个方法,如使用 nodejs 或者 ajax 加载。由于模板内部也提供了include语句,这里路径是相对于当前模板目录的,可以复写template.helpers.$include来完成相对路径转换。

前端模板引擎种类众多,技术也很成熟,但是在前端模板文件级组织这一块仍然有很大的发展空间,欢迎 markerking 一起探讨。

majinkai commented 10 years ago

我几乎比较了所有模板引擎,主要考虑了以下几点:

  1. 上手难度,团队人员较多,不希望有过多的学习成本
  2. 语法常规,要么特别新颖的语法(jade),要么就常规语法(art、ejs)
  3. 性能,经过比较,art综合评价胜出
  4. 支持include或extends

最终选出了art,期间也考虑过juicer,但是好久没更新了,放弃之。

因需要支持碎片化模板管理,所以参考了ejs在浏览器端对文件的支持,稍做改造,在性能上肯定没有document.getElementById(id)高,但是对我们内部系统来说足够了,而且art本身就做了cache,改造成加载文件也同样有cache效果,损失性能也就在使用XMLHttpRequest同步读取文件这块,这部分性能损失可以忽略了。

改造部分代码: 增加配置项extension后缀和路径配置

template.extension = '.tp' //自定义模板文件扩展名
template.basePath = '/' //自定义模板文件路径

template.get加入对id的是否为文件的判断

// 获取模板缓存
template.get = function (id) {

    // 验证id有效性
    if (typeof id !== 'string' && id === '') return null;

    var cache, hasExtension, source;

    // by marker:
    // 增加文件读取,判断是否匹配后缀
    hasExtension = id.length > template.extension.length && id.substring(id.length - template.extension.length) === template.extension;

    if (_cache.hasOwnProperty(id)) {
        cache = _cache[id];
    } else if(hasExtension) {
        source = template.getFile(id);
    } else if ('document' in global) {
        var elem = document.getElementById(id);

        if (elem) {
            source = elem.value || elem.innerHTML;
        }
    }

    if (source) {
        cache = template.compile(id, source.replace(/^\s*|\s*$/g, ''));
    }

    return cache;
};

添加获取文件内容方法

template.getFile = function(id) {
    // 内部常用chrome浏览器,因此将原生支持放在第一位
    var factories = [function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
    for(var i = 0; i < factories.length; i++) {
        try {
            var request = factories[i]();
            if (request != null)  {
                // false:同步读取模板文件
                request.open("GET", template.basePath + id, false);
                try {
                    request.send(null);
                } catch(e) {
                    return null;
                }

                if (request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) {
                    return null;
                }

                return request.responseText
            }
        } catch(e) {
            continue;
        }
    }
    return null;
}
aui commented 10 years ago

刚才新建了一个分支,2.1.0版本,主要提供了文件级模板加载功能,内置 NodeJS 实现版本。

https://github.com/aui/artTemplate/tree/2.1.0(Beta)

nodejs 版本(node-template-simple.js)实现方式:

var fs = require('fs');
var path = require('path');
var template = require('./template.js');

// 提供新的配置字段
template.path = __dirname;
template.extname = '.html';
template.encoding = 'utf-8';

// 重写加载模板源文件方法
template.loadTemplate = function (id) {
    id = path.join(template.path, id + template.extname);

    try {
        return fs.readFileSync(id, template.encoding);
    } catch (e) {

    }
}

// 重写`include``的实现方法,转换模板为绝对路径
template.helpers.$include = function (id, data, from) {

    from = path.dirname(from);
    id = path.join(from, id);

    return template.render(id, data);
}

module.exports = template;

支持在 nodejs 下直接传入路径,例如:

var template = require('../src/node-template-simple.js');
template.path = __dirname;// 设置模板目录,默认为引擎所在目录

var html = template.render('node-template/index', {
    title: '嵌入子模板',
    list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
});

console.log(html);

ajax 加载同样可以使用插件的方式接入,markerking 可以看看你这里是否可以使用我提供的接口实现你要的功能。

aui commented 10 years ago

不管内部实现如何,我们要给开发者暴露的是如下简单的接口:

一、使用路径调用模板:

template.render('index/main', data);

二、模板语法支持 include 语句复用模板:

{{include '../public/header'}}

这种方式其实就和服务端模板一样,非常适合大型项目。

后端环境很容易实现文件加载(正如 nodejs),浏览器端却十分麻烦,为了绕开浏览器的限制我采用了较为通用的预编译技术来实现:进击!前端模板工程化

majinkai commented 10 years ago

明白了,我去试试了,感谢第一时间为用户做这么详细的支持,我来自韩都衣舍