技术杂谈:建立前后端分离的前端项目

PUBLISHED ON AUG 14, 2018

    传统的网页程序是以后端技术为中心,由后端来处理网页路径 (routes) 并输出页面。现在的网页程序强调 SPA (single page application) 的概念,尽量减少重新载入整个页面的次数,改用 AJAX 局部更新页面内容;许多新的网页程序都会使用 SPA 来增进使用者体验,一些成功的实例像是 Gmail 等。这个概念就催生出许多的前端框架,像早先的 Backbone.js 和现在的前端三雄 (Angular, React, Vue) 等。

    在工程上,将前后端拆成两个项目也有一些好处,像是利于团队分工。在这个思维下,后端用来实现商业逻辑及桥接数据库,前端用来传接数据及输出页面;前端的程序设计师不会直接动到后端的项目,反之亦然。将项目拆开后,前端只要用静态网页主机就可以架起来,不需和后端共用同一个服务器。另外一个好处是将数据和输出解耦,同样的数据可以搭配不同的媒介,像是同一个后端,可用来桥接网页、行动装置、桌机等不同环境。

    这样做并不是没有缺点,主要的问题在于项目的复杂度会提升,而且需要一些额外的工作,像是需要建立用来传假数据的 mock server 等。并不是所有的网页程序项目都需要用这样的架构来建立,还是要依实际情形去评估。

    在这样的前提下,要怎么建立前端项目呢?由于前端技术选择很多,没有制式的项目架构,要由程序人自己去撰写样板程序代码和设定档来串接项目。本文提出一些通则,读者可依据自己喜好的网页技术来变通。

    整体架构

    我们建议使用巢状架构,并加入以下数据夹:

    • *www*:存放会输出到客户端的内容
    • *src*:存放实际撰写的前端程序代码
    • *test*:存放测试程序代码
    • (选择性) *mock*:存放 mock 服务器的程序代码
    • 根目录用来存放项目设定档和文件,像是 package.jsonREADME.md
    • 可再视需求加入其他目录,像是 Node 项目会自动加入 *node_modules*,将外部套件存在里面

    为什么要用巢状结构呢?因为在发布项目时,我们只要发布 www 数据夹的东西即可,其他的东西不会暴露出来,若要将发布流程自动化会比较简单。

    www 目录

    www 目录是实际发布项目时会传出去的文件,一般来说,会包含以下文件:

    • index.html
    • main.css
    • main.js

    index.html 是网页服务器常见的默认入口网页档名,建议沿用这个档名;其他的文件要放什么内容、用什么名称命名都仅是开发者的习惯。将 CSS 设定档和 JavaScript 脚本集中在一起,可以一次载入完成,运行上会较顺畅,但必要时仍可将文件拆开。如果要直接用原生的 CSS 和 JavaScript 撰写程序,将程序存在此目录即可。另外,既然将前端分离出来,通常也会使用 SPA,故此处仅有单一页面。不过,必要时仍可以使用多页面,不需刻意自我设限。

    src 目录

    src 目录则是存程序代码的地方。现在网页技术有许多的衍生语言,像是 SCSS 或是 TypeScript 等,这些语言无法直接在客户端运作,需要转换成相对应的原生程序代码后才能使用。这些语言的目的在于改善原生语言的缺点,但会使项目架构变复杂。由于这些衍生语言众多,建议可以用程序语言来区分,例如:

    • src/scss 用来存放 SCSS 设定档
    • src/typescript 用来存放 TypeScript 脚本

    这种安排方式的灵感来自于 Gradle。比较麻烦的地方在于,没有现成的项目生成器,要自己手动写设定档去串连编译的过程。笔者以为,网页前端技术繁杂,迭代又相当快速,很难写出一体应用的项目生成器。或许之后会有高人将这些过程自动化也说不定。

    test 目录

    test 目录存放测试程序。我们可以直接对源代码做测试,或是对生成的网页做测试,要看开发者自己的测试目标而定。前者较为简单,以该程序语言撰写测试程序即可;后者则要用一些网络爬虫来仿真使用网页的过程,测试程序会稍微复杂。

    设定档

    这部分通常会放在根目录,至于会用到那些设定档,则依使用到的工具而定。一般来说,会用 Npm 套件来管理项目,因为很多网页程序相关的工具用 Npm 套件发布,很难完全不用到 Node.js 生态圈的软件。目前来说,比较流行的编译自动化有 Grunt、Gulp、Webpack 等,由于这三套的用途有所重叠,只要选定自己较顺手的一套即可。

    除了使用原生的网页技术外,现在我们也会使用一些衍生语言来写程序,然后再转成等效的原生程序代码。这时候就要搭配前述的编译自动化软件来串接流程。由于这些衍生语言种类繁多,并没有什么一体适用的流程,所以要自己撰写设定档。其实这样做的效率很差,因为设定档只是辅助写程序的过渡手段,对产能没有帮助。建议不要频繁地换网页技术,选定好相关技术后,可以自己建个 Hello World 项目,之后就可以省下撰写撰写设定档的时间。

    前后端沟通

    对于这种架构不熟悉的读者,可能会对这种架构感到困惑,要如何和后端沟通呢?现代浏览器都有 CORS (Cross-Origin Resource Sharing) 的特性,可以存取不同网域的资源,传输数据不是问题。由于安全性的考量,CORS 默认是关闭的,要在客户端和伺服端做一些额外的设定。除非要支援一些旧浏览器,都可以用这个特性来撰写网页程序。

    开发用服务器

    在开发的过程中,我们还是需要一个网页服务器来仿真在网页上互动的情形。这里列出一些用于开发静态网站的网页服务器,这些软件的特性在于免设定,输入一行指令即可使用,在开发早期省下设定网页服务器的时间。本文所提的项目在网页上视为静态网页,适用于本节提到的这些工具。这里以 http-server (Npm 套件) 展示实例如下:

    $ cd path/to/project
    $ cd www
    $ http-server
    

    这样就可以立即开启一个网页服务器。

    Mock 服务器

    Mock 服务器和开发用服务器不同,前节所提到的开发用服务器是仿真网页的行为,而 mock 服务器用来仿真前后端传输。Mock 服务器的用意是在开发早期,前后端还没串起来时,先写一个简单的服务器,用来传输假想的数据。Mock 服务器不需实现真正的商业逻辑,也不需连接数据库,可以直接回传静态的 XML 或 JSON 文件即可。

    对于前端程序设计师来说,撰写 mock 服务器不应占掉过多时间。一个选项是用 express.js 撰写一个小型的服务器,因 express.js 也是 Node.js 生态圈的软件,学习起来相对容易。或者是使用一些专门的 mock 套件,像是 mock.js 等。

    后记

    笔者首次注意到这种架构是在学习 Dart 时。因为 Dart 一开始就强调前后端分离的架构,而且 Dart 的后端刚好相对贫乏,笔者经过一段时间才逐渐理解 Dart 这样设计的理由。这样的概念不限于 Dart 项目,在原生 JavaScript 或其他网页衍生语言 (如 TypeScript 等) 的项目也可使用;故笔者写下此文,供有需要的读者参考。