一次失败的HTML模块化尝试
前端最基本的工作就是写页面模板,在完成webpackTpl的环境搭建之后逐渐尝试使用HtmlWebpackPlugin
来生成模板。在多页面的项目中,往往存在相同的布局,因此开始了一次HTML模块化的尝试。PS:结局惨不忍睹。
在伟大的DRY
原则指导下,为了实现HTML模板的复用,必须实现某种类似于include
或import
的机制,将页面结构拆分成多个独立的文件组件(比如header, footer等),然后按需引入即可。由于最初就是使用HtmlWebpackPlugin来开发模板的,因此就从它开始入手。
(再次提醒,这次的内容真的是瞎折腾,大家看看就好了,不要被我带到坑里面去了~真是一把心酸泪。)
HtmlWebpackPlugin
在前面的webpack折腾记(二)中,总结出了使用HtmlWebpackPlugin的几个好处:
- 配置通用的模板变量及资源路径
- 支持循环和条件分支,方便模拟数据
- 样式脚本内联
在编写单页面的时候,由于只需要一个模板,因此上面的功能基本上就能满足开发需求了。
不幸的是,HtmlWebpackPlugin
虽然是ejs
的语法,但是,它并不支持ejs的<% include common/header %>
这样的语法,在多页面的情况下肯定是不行的。查了半天资料,网上推荐的html-loader
和ejs-loader
也不能解决(可能是我的打开方式不对)。
转念一想,既然HtmlWebpackPlugin不支持,那我们可以单独编写ejs模板,然后再将模板输入给HtmlWebpackPlugin进行打包不就可以了吗?
ejs
既然是完全采用ejs来进行开发,那么“将结构拆分成组件然后按需引入”的目的就能够轻松使用include
完成。
模板传参
有时候需要为模板传入参数,然后渲染出对应的测试数据,文档中给的示例是
<ul>
<% users.forEach(function(user){ %>
<%- include('user/show', {user: user}) %>
<% }); %>
</ul>
文档中还指出<% include user/show %>
这样的方式已经停止更新了,但是我还是比较喜欢这种写法,因此还有一个折衷的解决办法:在ejs配置中传入一个全局对象,然后通过对象上的属性来进行参数传递
// index.ejs
<? var opt.msg = "heallo "?>
<? include common/header ?>
// header.ejs
<h1><? opt.msg ?></h1>
(PS:是不是很无语~)
热加载
要在开发时使用ejs并实现热加载,我采用的解决办法是gulp + gulp-ejs
,即单独开启一个gulp任务用来监听开发模板的变化,并将编译后的模板输入到HtmlWebpackPlugin的模板目录,最后由webpack实现热更新。
gulp.task('ejs', function(){
let { SRC_DIR } = config;
gulp.watch(`${SRC_DIR}/ejs/*.ejs`, function (e) {
let file = e.path;
gulp.src(file)
.pipe(ejs({},{
delimiter: "?"
}).on("error", function (e) {
console.log(e)
}))
.pipe(gulp.dest(`${SRC_DIR}/tpl`));
})
});
这里就有点绕了,由于之前的webpackTpl
搭建的环境有一个任务是输出生成环境后台需要的PHP模板,因此现在我们的模板生成路径就变成了:
ejs > htmlplugin template > html/php
这里强行把模板拆分然后再整合在一起,就感觉像是吃饱了撑的(/捂眼笑)。
界定符
由于ejs的默认界定符是<% %>
,这跟HtmlWebpackPlugin的一模一样。如果不做任何操作,则HtmlWebpackPlugin得到的就是纯粹的HTML文本,为了使用HtmlWebpackPlugin的某些特性(向webpackTpl兼容),我的处理是直接修改了gulp-ejs
的界定符参数配置,然而文档上面并没有说明如何进行配置,查看源码可以发现
module.exports = function (data, options, settings) {
// ...
file.contents = new Buffer(
ejs.render(file.contents.toString(), data, options)
)
}
data
参数为传入模板的变量,options
为ejs相关的配置,所以直接将界定符设置为<? ?>
形式。(PS:这完全不是在倒腾HTML模块了...)
.pipe(ejs({},{
delimiter: "?"
})
万万没想到漏了一点:php采用的也是<?php ?>
界定符,也就是说如果想要在模板中直接输出原始的php代码,编译肯定会报错的(为什么要在ejs模板上写php代码呢?因为我们后台是php~~),然后我的解决办法是使用函数输出php代码字符串
pipe(ejs({
opt: {},
// 输出php字符串变量
php(str){
return '<?php ' + str + ' ?>';
},
},{
delimiter: "?"
})
然后在模板中调用<?- php("echo 1"); ?>
即可。现在博客写到这里,我真想知道当时的我到底是犯了什么浑~~弱智啊!!
动态引入
本以为搞完上面的事情,整个活就可以告一段落了:
- 采用原生的ejs开发模板,畅快地使用模板变了,循环分支,组件引入等功能
- 采用
gulp-ejs
动态编译模板,配合webpack实现热加载
后来突然想到,这特么输出的还是一个完整的html页面啊,最后切换环境输出给后台的时候,还是需要把模板的组件提取出来~这完全是重复的工作嘛(现在除了前端页面,后台的路由和模板数据也得我负责)。所以就想着干脆把ejs的include
替换成动态的加载吧:
- 在开发时使用正常的ejs引入
- 在打包时直接输出php框架的组件引入
为了实现这个目的,又写了一个全局的ejs方法
load: function(tplPath){
if (isDev){
if (!~file.indexOf("*")) {
tplPath += ".ejs";
let absPath = path.resolve(path.dirname(file), tplPath);
return ejs.render(`<% include ${tplPath} %>`, {}, {filename: `${path.dirname(absPath)}`});
}else {
}
}else {
tplPath += ".php";
return `<?php $this->load->view("${tplPath}"); ?>`
}
},
上面代码的具体细节就不深究了(反正现在我已经放弃了),主要就是为了实现上面的在不同编译环境的模板输出。在模板源文件上调用<?- load("commomn/head")?>
即可
反思
上面的工作大概折腾了半天时间,整个流程总算是能跑起来了。当我把之前十来个模板页面上的公共组件提取出来之后,脑海中出现了一个声音:卧槽,有病吧?
静下来想一想,最初的目的只是为了在模板中加载一个外部的组件,谁知道却围绕着ejs折腾了这么多无关的事情,简直就是本末倒置啊。先理一理:
- 为了实现模块,复用公共组件,采用了ejs进行开发
- 为了打包,使用了webpack
- 为了区分开发环境和线上环境,为整个流程进行了一系列的适配处理
- 最后输出了后台需要的PHP模板文件
突然发现,做的这些事,无非是将ejs的组件编译成了PHP的组件(还得费时费力的去适配和调整模板),后台语言天生就支持文件导入,像Laravel
这样的框架提供的blade
模板更是强大,既然最后的生产目的都是为了对接数据,一开始直接用PHP写模板不就得了吗?为什么要钻进“通过webpack然后再转成PHP的模板的死胡同“呢?瞬间醒悟。
另外,维护的方面来讲,以前只需要调试本身的模板即可,现在呢?需要深入好几层,才能发现最初的源代码,虽然开发看起来高大上了(开发效率有没有提高我就不确定了),但是无形之中却增加了维护的难度,后面如果有同事不明白你的工作流程,直接在编译的模板上面进行更改,肯定会骂死你的。
现在的前端环境发展十分迅速,像我这种半路杀进来的人感觉也十分明显,各种工具,各种框架层出不穷,圈子也比较浮躁(感觉我自己也受到了感染,这次HTML模块化的尝试就是一个例子)。
然而,术业有专攻,传统也并不是一无是处,找到适合业务的技术框架和开发环境才是最主要的,脱离生产环境高谈技术的都是耍流氓。另外,一定不要浮躁!切记。
PS:貌似把blade
的模板用JS实现一遍也不错哦,这样模板就可以完全通用了~打住打住....
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。