后端的动态模板
Java后端通常会使用ftl
(freemarker template language)模板文件来动态返回前端页面。这个工作,通常还可以用jsp
、php
文件来实现。但这些动态模板的实现,通常是在已有的html文件上对特定的需要做动态处理的部分做改写。这对小项目来讲没什么不对。可如果你的页面数量足够多,维护它们将成为一件异常困难的事情。
Nodejs大前端技术
在目前的大前端技术栈下,Nodejs的各种框架让前端开发变得规矩不少。我的一个理解是,传统前端的html+css+js的技术栈的最大问题在于其模块化、组织能力像是一个教学语言,应有的语句控制和代码复用的技术,都显得苍白无力。
就html的编写来讲,几乎不存在一种类似函数的复用方式,能够简化重复的UI component的生成。你只能不断地去写一些重复的、杂乱的代码。整体上来讲,这不仅难以做后期的维护,也无法轻易地看懂其间的代码逻辑。
一句话来讲,这些代码非常类似于机器代码或者汇编代码。没有高级语言的精准控制和抽象层去对代码做宏观把控。
Pugjs
是一个很好的html预处理项目。它的基本想法是:
不要去直接编写“底层”的html代码,而是用自己定义的一套语法去编写
pug
文件。
通过这个pug
文件去生成出html代码。特别的,在它的语法中,你不必再写一大堆的尖括号和与前后呼应的tag。如同Python,仅仅依靠代码的对齐方式,就可以自动识别相应的作用域范围。例如
<div>
<ul>
<li> First tip </li>
<li> Second tip </li>
<li> Third tip </li>
</ul>
</div>
这样语义简单、语法繁琐的一堆代码,在pug
下可以简化为
div
ul
li First tip
li Second tip
li Third tip
但这还不是最诱人的技术,因为这无非是加入了一些语法糖。最为诱人的是pug
提供的函数,它能够定义一个函数去生成某个组件。
例如,如果你需要定义一组table
,每个table
仅仅是表头或者其中一部分的数据不一样,你该如何处理?传统的方式当然是复制粘贴一堆模板代码,然后一个个地去修改里面的数据。
而pug
的处理方式就要好太多,完全符合将数据和代码分离的思想。定义函数:
mixin leftbox-gen(dataObj)
table.table
thead
tr
th(scope="col") #{dataObj.title}
tbody
each area in dataObj.areas
tr
td
.box-title #{area.name}
ul
each subarea in area.subareas
li
a(id=subarea.id, href=subarea.url) #{subarea.name}
这样就可以根据通过定义json
格式的dataObj
去引用函数:
+leftbox-gen(cs_leftbox_data)
你通过不同的json
数据,就能够生成不同的table
出来。这就实现了代码的模块化和以及数据和业务代码的分离。要做出新的table
component,你只需要改变数据就可以了。
这样的实现方式在别的高级语言中是很常见的,但是在传统的前端代码中,这常常难以见到。原因就在于,html代码更像是没有抽象层的机器代码,只是一大堆的实际操作,而缺少抽象层的高效管理。
前端预处理和后端动态模板的结合
像pug
这样优秀的工具,如果能够用来管理后端的ftl
模板当然会相当合适。优秀的语法糖、代码模块化、数据和业务逻辑的分离,实在是相当诱人的选择。
但这样的理念真要实施在生产代码中,特别是用来重构已有的legacy code时,就不大容易了。
例如,pug
只能生成html代码,且生成出来的位置通常是在一个统一的地方。可ftl
代码却分散在各个不同Java工程的不同目录之下。这两者很难统一到一起。
或许一个直接的想法是,不如直接把所有的ftl
都放到一个地方,这样就不用把模板语言分散到各个不同项目的不同文件夹里,而难以维护。
但这种方案带来的一个麻烦是,如果真的把后端的ftl
文件挪动了位置,那么你后端代码的接口部分就不得不做修改。而这样的接口部分其数量并不少。既要做出大量的修改,还要保证它们的正确性,并不是一件轻松的事情。
经过大量的思考和尝试,我得出的一个解决方案是:
使用
Pugjs
生成出统一的ftl
文件,放在同一个公共资源文件夹下。让每一个具体项目下的ftl
文件中,直接include
这个公共资源文件夹中ftl
内容。
这种做法的一个精妙之处是:它将ftl
文件当作函数接口来使用。后端Java代码调用ftl
文件可以看作是函数调用。而函数实现并不需要直接放在这个ftl
文件里,而是可以放在别的地方做引用。这就把实现和调用部分,通过一个单独的文件分离开了。
这里虽然处理的是后端模板文件和前端的一个结合,但其思想可以利用在别的地方。如果一个模板文件具备了include
功能,便可以把模板文件本身当作接口,从而将实现与定义分离。