16
本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。
原文地址:https://segmentfault.com/a/1190000008203380
如果您对本系列文章感兴趣,欢迎关注订阅这里:https://segmentfault.com/blog/array_huang

前言

近年来前端领域发展迅猛,前后端分离早已成为业界共识,各类管控系统(to B)上个SPA什么的也不值一提,但唯独偏展示类的项目,为了SEO,始终还是需要依赖于服务器端渲染html。

过往也曾尝试为SPA弥补SEO,但现在看来,效果虽然达到了,但工作量也大大地增加(因为后端用的PHP,不能做到前后同构)。

虽然无法改变依赖服务器端渲染这一现实,但我们可以去勇敢地拥抱它,用前端的坚船利炮(aka webpack),把服务器端模板层给啃下来!

前导知识

两个阶段

整个前端项目,以本文主题的视角来看,可以分为两个阶段:

纯静态页面开发阶段

在这个阶段里,一切开发都跟静态网站无二致,按UI稿切好页面搞好交互,要用到ajax请求API的也尽管写,跟后端的协作点仅在于API文档。

传统前端的工作也就到这里为止了,但对我们来说,目前的成果并不是我们最终的交付;因此,注意了,在这个阶段我们是可以“偷懒”的,比如说,一些明显应该由服务器端循环生成的部分(商品列表、文章列表等),我们写一遍就OK了。

动态页面改造阶段

这就是所谓的“套页面”,传统来说是由后端来做的,实际上后端也是苦不堪言,毕竟模板不是自己写的,有时还是需要改造一番,而这正是我们前端要大力争取的活。

在这个阶段里,我们的主要工作是按照后端模板引擎的规则来撰写模板变量占位符,当然这里面也不会少了循环输出和逻辑判断,另外也可能需要用到后端定义的一些函数,视项目需求而定。

在两个阶段里来回往返

这两个阶段不一定是完全独立的,有需要的话也是可以做到来回往返的。

那什么时候才叫做“有需要”呢?举个例子,当你把原先的静态页面都改造成需要后端渲染的页面模板后,却发现后端此时并未准备好相应的模板变量,而你此时又需要对页面的UI部分进行修改,那么你就很被动了,因为改好的这些页面模板根本跑不起来了。有两种解决方案:

  • 参考API mock的思路,来个模板变量 mock,这就相当于一直留在动态页面改造阶段了。
  • 回到纯静态页面开发阶段,让页面不需要后端渲染也能跑起来。具体怎么做呢?

    1. 区分开两个阶段,使用不同的webpack配置。
    2. 在我们构建生成页面的前端模板(注意分清与后端模板的区别),判断(判断依据看这里)本次执行webpack打包是在哪个“阶段”,继而选择是生成静态(且完整)的element,还是带有模板变量占位符的element。这样一来,我们就可以随时选择在不同的阶段(或称环境)里进行开发了。

改造开始

本文着重介绍如何将静态页面改造成后端渲染需要的模板。

配合后端模板命名规则生成相应模板文件

不同项目因应本身所使用的后端框架或是其它需求,对模板放置的目录结构也会有所不一样,那么,如何构建后端所需要的目录结构呢?

在静态网页阶段,我习惯把html/css/js都按照所属页面归到各自的目录中(公用的css/js也当然是放到公用目录中),看HtmlWebpackPlugin配置:

pageArr.forEach((page) => {
  const htmlPlugin = new HtmlWebpackPlugin({
    filename: `${page}/index.html`, // page变量形如'product/index'、'product/detail'
    template: path.resolve(dirVars.pagesDir, `./${page}/html.js`),
    chunks: [page, 'commons/commons'],
    hash: true,
    xhtml: true,
  });
  pluginsConfig.push(htmlPlugin);
});

而在改造阶段,则放到后端指定位置:

pageArr.forEach((page) => {
  const htmlPlugin = new HtmlWebpackPlugin({
    filename: `../../view/frontend/${page}.php`, // 通过控制相对路径来确定模板的根目录
    template: path.resolve(dirVars.pagesDir, `./${page}/html.js`),
    chunks: [page, 'commons/commons'],
    hash: true,
    xhtml: true,
  });
  pluginsConfig.push(htmlPlugin);
});

此时我模板目录结构是这样的:

│  
├─alert
│      index.php
│      
├─article
│      detail.php
│      index.php
│      
├─index
│      index.php
│      
├─product
│      detail.php
│      index.php
│      
└─user
        edit-password.php
        modify-info.php

这里需要注意的是,我的前端项目目录实际上是作为后端目录里的一个子目录来存放的,这样才能依靠相对路径来确定模板文件存放的根目录位置。

处理站内链接

对于站内链接,我建议在前端模板里使用一个函数来适配两个阶段:

{
  /* 拼接系统内部的URL */
  constructInsideUrl(url, request, urlTail) {
    urlTail = urlTail || '';
    let finalUrl = config.PAGE_ROOT_PATH + url;
    if (!config.IS_PRODUCTION_MODE) {
      finalUrl += '/index.html' + urlTail;
      return finalUrl;
    }
    return `<?php echo cf::constructInsideUrl(array('module' => '${url}'), $isStaticize)?>`;
  },
};

在前端模板里这么用:

<a href="<%= constructInsideUrl('index/index') %>">
  <img src="<%= require('./logo.png') %>">
</a>

这样做,就能分别在静态页面阶段和后端渲染阶段生成相应的超链接。再者,在后端渲染阶段,我们生成出来的也不一定是一个完整的url,可以像我上述代码一样,生成调用后端函数的模板代码,从而灵活满足后端的一些需求(比如说,我的项目有静态化的需求,那么,静态化后的站内链接跟动态渲染的又会有所不同了)。

处理模板变量

这一块其实我要说的不多,无非就是按照后端模板引擎的规则,输出变量、循环输出变量、判断条件输出变量、调用后端(模板引擎)函数调整输出变量。

关键是,我们需要拿到一份模板变量文档,跟API文档类似,它实际上也是一份前后端的数据协议。有了这份文档,我们才能在后端未完工的情况下,进入动态页面改造阶段,并根据其中内容实现模板变量 mock

争讨模板布局渲染权

关于利用模板布局系统对多个页面共有的部分实现复用,在之前的文章里已经提及了,我设计该系统的思路恰恰是来自于后端模板渲染。那么,在前后端均可以实现模板布局系统的前提下,我们应如何抉择呢?我的答案是,前端一定要吃下来!

从前端的角度来看:

  • 我们在纯静态页面开发阶段的产物就已经是一个个完整的页面了,再要拆开并不现实。
  • 由于在webpack的辅助下这套模板布局系统功能相当强大,因此并没有给整个项目添加额外的成本。

从后端的角度来看:

  • 服务器拼接多个HTML代码段本身也是有成本(比如磁盘IO成本)的,倒不如渲染一个完整的页面。
  • 在公共组件的分治管理上不会有很大变化,只不过以前是一个一个组件渲染好后再拼在一起,而现在是把各个组件的数据整合在一起来统一渲染罢了。

总结

在后端渲染的项目里使用webpack多页应用架构是绝对可行的,可不要给老顽固们吓唬得又回到传统前端架构了。

示例代码

诸位看本系列文章,搭配我在Github上的脚手架项目食用更佳哦(笑):Array-Huang/webpack-seedhttps://github.com/Array-Huang/webpack-seed)。

附系列文章目录(同步更新)

本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。
原文地址:https://segmentfault.com/a/1190000008203380
如果您对本系列文章感兴趣,欢迎关注订阅这里:https://segmentfault.com/blog/array_huang

如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

44 条评论
ANDREW · 2017年02月12日

请问作者,你的主页域名array_huang的下划线是怎么做到/申请的呀,很酷啊!愿意分享一下方法吗?

回复

0

额?这个要申请的么?我的ID就是array_huang呀

array_huang 作者 · 2017年02月12日
zipoopiz · 2017年06月24日

处理模板变量那一块能具体说说吗?你是用类似constructInsideUrl的方法来区分开发阶段和生产阶段吗?

回复

0

对的,就是适配一下嘛,毕竟开发环境和生产环境的页面路径可能会有所不同

array_huang 作者 · 2017年06月24日
0

@array_huang 比方说我用了php的某个mvc框架,那么,所有后台传到view的变量我都要有相对应的mock变量是吧?是不是得写一个类似constructInsideUrl的函数,比如:
function getValue(key) {
if (!config.IS_PRODUCTION_MODE) {

 return mockData[key];

} else {

 return `<?php echo $${key}?>`

}
}
然后在每一个嵌套数据的地方都调用一下这个方法?

zipoopiz · 2017年06月25日
0

@zipoopiz 理论上来说是的,但是这样做其实挺麻烦的。好处是这样你可以随时脱离PHP的环境,随时可以作为静态页面来预览。但反过来想,如果你不需要脱离PHP的环境,后续的工作统一作为PHP模板来开发的话,那就不需要了。

array_huang 作者 · 2017年06月26日
时间煮雨 · 2017年07月18日

npm-scripts里面的脚本是在哪里调用了?找了半天也没找到调用入口

回复

0

噢,自从升级到webpack2了以后就没有用了

array_huang 作者 · 2017年07月18日
0

好吧 我找了半天....但是你也没引用clean-webpack-plugin呀,webpack2能自动清除build目录里的东西么

时间煮雨 · 2017年07月18日
0

@时间煮雨 clean-webpack-plugin我之前用过,但是不知道为啥一点效果都没有。其实之前升级webpack2把那脚本给去掉也是有原因的,嘛,算是妥协的结果,不过我现在想到解决的方法,待会加上去。

array_huang 作者 · 2017年07月18日
时间煮雨 · 2017年07月18日

还有一个问题,你的dll文件夹没有在layout里面的header.ejs里面引用了,这是不需要引用了还是怎么?和你这个教程讲的优点出路了,为什么不引用dll文件夹的内容了???能帮忙解释下吗

回复

0

当初是要每次都要编译bootstrap,拉低了webpack的编译速度,所以才用的dll,后来不需要编译bootstrap了,自然dll也就不是那么必要了。这种事情也是看自己的,你引用的第三方库很多的话,用dll还是帮助很大的。

array_huang 作者 · 2017年07月18日
时间煮雨 · 2017年07月18日

能不能针对你升级后的webpack多页面架构方案出一个教程呢,或者升级指南呢?还有里面的ie-fix文件夹是怎么用的本人也没看太明白,还望作者能够再写一点,谢谢了!!!

回复

ペイン · 2017年07月19日

最近也在计划做旧项目的webpack改造,后台是thinkphp做的,想知道搭建一个类似vue-cli这样的脚手架,如何去组织这些html模板,让我很头疼,本身就要启动前后端俩个服务 如何整合起来,楼主如果有思路烦请告知下,不胜感激啊

回复

0

我也是用的thinkphp,这篇文章就是我在整合后得出的经验之谈

array_huang 作者 · 2017年07月19日
谢舟哦哦哦 · 2017年08月16日

处理模板变量那块 是直接手写 php模板吗?

回复

0

@array_huang

谢舟哦哦哦 · 2017年08月16日
0
array_huang 作者 · 2017年08月16日
谢舟哦哦哦 · 2017年08月16日

开发环境和生产环境的模板都是手写的php模板吗 就是没有html 这一环了吗 对吗? 最后一个完整的页面打包出去 是这样吗 那么就要搭建相应的后台环境吗

回复

0

看你喜欢咯,反正最终产物肯定是php模板

array_huang 作者 · 2017年08月16日
0

如果是php模板,那肯定就是要搭配后端来使用了

array_huang 作者 · 2017年08月16日
0

为了调试方便啊,开发环境我弄那个html, 生产环境弄php模板。 不方便是,这样就有2套模板 ,要同时改 维护不方便啊 有神马破法吗

谢舟哦哦哦 · 2017年08月16日
谢舟哦哦哦 · 2017年08月16日

或者说 我只用webpack 做js css的依赖打包和开发调试。 还是和以前一样给后端静态html套模板,他们套完以后。 我可以用神马工具 在jscss修改以后添加md5后缀啊, 然后自动改变jscss吗

回复

0

打包好的php模板就跟webpack完全脱离关系了,也就没有然后了

array_huang 作者 · 2017年08月16日
MARKORMARK · 2017年09月09日

老哥你有没有发现项目跑起来之后页面先加载出来HTML然后再渲染CSS,中间有个时间差会显示没有CSS的原始dom的问题?

回复

0

生产环境就不会有这个问题了,这是正常表现

array_huang 作者 · 2017年09月09日
shlee · 2017年09月12日

大神,我想生成jsp模板,该怎么办呢

回复

0

一样的,你在里面直接写jsp的代码就行了

array_huang 作者 · 2017年09月12日
0

@array_huang 谢谢,已经搞定

shlee · 2017年09月12日
fedorjia · 2017年10月06日

html-webpck-plugin在开发的时候,生成的模板文件在内存里,请问后端的数据 怎么渲染到模板里?

回复

0

所以就不能用webpack-dev-server,要直接build,然后用--watch来监视文件变动

array_huang 作者 · 2017年10月06日
0

两种解决办法,一是从内存读,如果是node后端,可以从node服务启动webpack-dev-server,指定mfs,这样就可以读了。第二种是通过http获取。

Jokcy · 2017年11月20日
Jokcy · 2017年11月20日

容我问个小问题,如果我在HTML里面引用了图片资源,HTML-webpack-plugin会编译这个路径么?

回复

0

会编译的,但是你需要用特定语法,比如说ejs的语法来require

array_huang 作者 · 2017年11月20日
0

@array_huang 能具体说说吗?是指要指定用ejs-loader来处理模板?

Jokcy · 2017年11月20日
0

@Jokcy 这篇文章不就是讲了么

array_huang 作者 · 2017年11月20日
MrXu · 2018年04月30日

大神,你好。我运行了npm run dev之后使用sublime启动了服务,静态资料路径不对

回复

Benes · 2018年07月04日

图片,css中的图片资源的引用你是怎么处理的

回复

载入中...