傳統的網頁程式是以後端技術為中心,由後端來處理網頁路徑 (routes) 並輸出頁面。現在的網頁程式強調 SPA (single page application) 的概念,儘量減少重新載入整個頁面的次數,改用 AJAX 局部更新頁面內容;許多新的網頁程式都會使用 SPA 來增進使用者體驗,一些成功的實例像是 Gmail 等。這個概念就催生出許多的前端框架,像早先的 Backbone.js 和現在的前端三雄 (Angular, React, Vue) 等。
在工程上,將前後端拆成兩個專案也有一些好處,像是利於團隊分工。在這個思維下,後端用來實作商業邏輯及橋接資料庫,前端用來傳接資料及輸出頁面;前端的程式設計師不會直接動到後端的專案,反之亦然。將專案拆開後,前端只要用靜態網頁主機就可以架起來,不需和後端共用同一個伺服器。另外一個好處是將資料和輸出解耦,同樣的資料可以搭配不同的媒介,像是同一個後端,可用來橋接網頁、行動裝置、桌機等不同環境。
這樣做並不是沒有缺點,主要的問題在於專案的複雜度會提升,而且需要一些額外的工作,像是需要建立用來傳假數據的 mock server 等。並不是所有的網頁程式專案都需要用這樣的架構來建立,還是要依實際情形去評估。
在這樣的前提下,要怎麼建立前端專案呢?由於前端技術選擇很多,沒有制式的專案架構,要由程式人自己去撰寫樣板程式碼和設定檔來串接專案。本文提出一些通則,讀者可依據自己喜好的網頁技術來變通。
整體架構
我們建議使用巢狀架構,並加入以下資料夾:
- www :存放會輸出到客戶端的內容
- src :存放實際撰寫的前端程式碼
- test :存放測試程式碼
- (選擇性) mock :存放 mock 伺服器的程式碼
- 根目錄用來存放專案設定檔和文件,像是 package.json 或 README.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 等) 的專案也可使用;故筆者寫下此文,供有需要的讀者參考。