FIS+Webpack在後端模板場景下的混合開發模式探索
原創文章,轉載請註明出處。
概述
本文介紹了一種FIS + Webpack的混合前端工程化方案,可以較好地適配後端模板場景下的前端開發,經過具體項目實踐和沉澱,我們認為該方案對於提升前端開發效率、改進開發模式、提高代碼質量都有積極的影響。
業務背景
在百度外賣(乃至百度系)的平台項目中,廣泛存在的一種模式是:
這種模式的幾個特點:
- 用戶的請求返回依賴於後端模板解析。
- 前端通過fis / fis-plus的smarty方案,配合後端做資源載入、模塊化開發、代碼部署等。
- 用戶的首次請求基於後端路由(如app/controller/action的模式)。
- 比起純前端的工程,可以載入公共數據(如許可權、通用方法等)和介面數據,減少請求次數。
在這種場景下,FE同學一般的開發模式為:
- 配合RD同學定好tpl模板路徑。
- 通過fis release到RD同學的開發機進行開發、聯調等。
- 聯調完成,fis進行代碼打包、壓縮等,上線部署到對應線上ODP server。
這種開發模式下,前後端的分工明確,流程清晰,便於執行,代碼可以分模塊組合、上線;百度外賣的許多平台化項目,都是基於這種模式下的、十分龐大的系統。
但是近些年,隨著前端社區和技術的迅猛發展,這種開發模式已經逐漸開始暴露出一些問題。
後端模板場景下的fis困局
不可否認,fis作為前端工程化集大成的解決方案,本身的設計思想、開發範式仍有很大的實用價值。但是在2015年fis3發布之後,本身的資源投入似乎在逐步減少,核心開發者也趨於沉寂(從github的commit可以看出),而15年至今,前端社區日新月異,新的技術、框架不斷湧現,fis在技術更新的浪潮中已經略顯乏力。
舉例說明:
包管理體系:fis-components vs npm
fis曾經有自己的組件化體系fis-components, 可以通過 fis install [dependency]的方式安裝、管理項目依賴。
但是這種方式的最大痛點是:無法自由添加組件,必須通過給fis官方提PR的方式來添加組件。
這樣不僅增加維護成本,而且用戶自由度也很低。
fis-components的組件數量仍很少(與npm上的包數量是天壤之別),此方案現在基本已經被廢棄,fis官方現在也推薦使用npm來管理第三方組件,推出了node_modules的方案fis3-hook-node_modules。
在一些純前端的項目中 fis3-hook-node_modules 可以工作得很好。
但是在後端模板場景下,以百度外賣某平台為例:
平台由多個前端代碼模塊組成,其中:
- common模塊為公共模塊,左側的導航欄都由common模塊生成;
- 右側為業務模塊,繼承common模塊的布局模板,承載具體業務邏輯。
前端同學平時做的基本是業務模塊開發。一個業務模塊的smarty模板如下:
{%extends file="common/page/layout.tpl"%}{%block name="page-main"%} <div id="app"> </div> {%script%} require("namespace:widget/xxx/index.js").init(); {%/script%}{%/block%}
可見,業務模塊的smarty模板繼承了common的layout模板。在這種跨模塊引用的場景下, 由於fis3-hook-node_modules的解析機制,會把這部分當作npm模塊來解析,導致無法編譯:
// 使用fis編譯時會無法解析[WARNI] Can』t resolve nerve_common/page/layout.tpl in file [/page/index.tpl], did you miss npm install nerve_common?
所以在這種場景下,無法使用fis3-hook-node_modules。這樣造成業務模塊中的第三方依賴只能依靠fis-components管理,或自行複製粘貼。對依賴的管理回到了手工時代。
另外,fis3-hook-node_modules也有自己的一些問題,比如對軟鏈形式的包(如cnpm)支持不好,對像vue這樣需要alias配置的包處理不便等。
開發效率:liveReload vs hotReload
前端同學應該對這兩個名詞非常熟悉。
fis本身是支持liveReload的,通過開啟-L參數即可。liveReload會啟動一個Server, 在代碼修改時可以通知瀏覽器刷新頁面,避免了手動刷新。
而最近幾年webpack為代表的構建工具中提供了「熱更新」,即hotReload(HMR, Hot Module Replacement)的能力,即:避免刷新頁面,只重新載入頁面中受代碼修改影響的部分。
hotReload相對於liveReload有很大的效率提升,設想以下場景:
- 一個表單填寫需要3個步驟,如果使用liveReload, 要調整第3個步驟的相關代碼時,需要再手動填寫1,2步驟,觀察第3步驟的效果。
- 需要對應的過濾條件才能顯示的UI,修改代碼後需要填寫對應過濾條件來「恢復現場」。
而hotReload可以很好地解決這些問題,一言概之,這種能力縮短了我們初始化應用及手動恢復應用狀態的時間。特別是對web前端這樣,UI代碼占很重一部分工作,隨時需要查看應用樣式的編程任務來說,這點更為重要。
但是hotReload本身不是銀彈,還需要對應的前端框架寫入組件級別的熱更新API(loader)才能工作。像React、Vue、Angular這類流行前端框架均已實現了webpack的HMR; 但是fis本身似乎沒有開發HMR的計劃。
異構解析:fis-parser vs webpack-loader
一般地,對於一些異構語言,需要相應的解析處理。例如:
- Jade/pug/tmpl => html;
- Less/Sass/Stylus => css;
- ESnext/JSX/Typescript/Coffee => js
在fis3的構建流程中,始終是同步的;而webpack-loader支持同步和非同步。
現在社區中的很多庫提供都是非同步的介面,外賣的前端同學可能體驗比較深刻的例子就是Vue, 官方有支持webpack的vue-loader, 支持Browserify的vueify。
而fis並沒有官方的fis-parser-vue, 現在只有民間開發者在維護一些fis-parser-vue的方案 ;而至今為止仍沒有在功能性方面和官方的loader媲美的parser插件。
這是其中的一個例子。主要的不便在於:
- 如果loader本身的處理是非同步的,要在fis中能使用需要改造成同步的,而且都是非官方的;
- loader本身的升級會帶來一些新特性(如css-modules), 相應的就會給fis-parser帶來維護成本。
- 新的技術、框架一般會適配主流構建工具而不會適配fis, 如果需要使用,就會在寫各種fis-parser上疲於奔命。
回顧和目標
說了這麼多,可以簡單回顧一下我們現有的業務場景:
- fis + node_module的方案並不能很好適配後端模板項目下的前端開發,造成依賴管理效率低,代碼冗餘等;
- fis release -w 到RD開發機的開發效率偏低 (特別是在項目文件較多的情況下);
- fis3-smarty / fis-plus方案下,很難去享受和嘗試一些新技術帶來的紅利。
那麼基於此,我們的目標就是,在後端模板的項目場景下:
- 能使用npm進行依賴管理;
- 能使用新的開發方式提高效率,比如hotReload;
- 能夠緊跟社區的腳步,方便地使用新技術來提高生產力。
方案的探索
使用webpack構建
基於現在webpack的主流地位,很容易想到使用webpack來構建項目。但是純webpack方案在這種場景下是行不通的,原因很簡單:無法解析smarty模板。
在smarty模板依賴後端結合fis-smarty-plugin進行解析的客觀條件下,顯然是不能搞一個smarty-loader進行解析的;即便做到了解析smarty語法,在webpack的編譯期也無法對跨模塊的引用進行依賴解析,如前面的common模塊的layout模板。
使用fis結合node_modules
前文已經有過詳細介紹,還是存在著無法跨模塊解析的問題。此處不再贅述。
fis + webpack混合方案
那麼我們換一種思路:讓fis負責smarty模板的解析;webpack用於構建業務代碼。那麼這樣,至少npm依賴管理是毫無壓力了,webpack天生就對node_modules非常友好。
項目中的業務代碼均由webpack構建產出,假設webpack構建產出的模塊為bundle.js, bundle.css, fis在smarty模板中require這些產出文件,並把這些bundle推到RD開發機。這樣的缺點也很明顯:
- 本地機器需要同時開啟webpack --watch和fis3 release --watch, 在模塊文件較多的時候會佔用較多機器資源,造成機器卡頓。
- 仍然無法享受hotReload。
那麼,有沒有一種方法可以兩全其美呢?在這裡我們的目標進一步被縮小:
- 不需要在本地啟兩個服務;
- 在RD開發機頁面上實現hotReload。
我們知道,具有hotReload特性的 webpack-dev-server 一般的使用場景是本地開發(在本地啟動一個http server),而我們顯然也無法在RD的開發機上「布署」一個webpack-dev-server。
但是,解決問題的鑰匙也在這裡:
能不能本地啟動一個webpack-dev-server, 然後RD開發機連接這個server?
經過實踐,我們發現這樣是可行的,而且進行了方案的整理、開發和實踐,最終已經運用到線上項目中。
方案介紹
我們現在採用的方案的「指導思想」可以一句話概括:
讓fis和webpack各司其職,各自做自己擅長的事。
webpack擅長的事:
- 與node_modules無縫結合;
- hotReload;
- module依賴打包;
- 代碼規範檢查,比如eslint(fis也可以做,但是我們認為webpack這種可以實現在頁面上加全局layer提示的方式更加友好);
- fis不具備的一些工程化的實用插件(如用於分析源碼文件佔比的webpack-bundle-analyzer)
fis擅長的事:
- 資源定位能力,配合smarty模板、map.json資源表結合後端實現;
- 方便地推送到遠端機器的能力;
- 依賴聲明和同名依賴(sameNameRequire)
方案目的也是最大化二者的能力來為我們服務。
由於fis和webpack都是「命令行工具」, 所以我們可以使用npm scripts來將二者的工作流結合。
開發階段
在開發階段,開發模式如下圖:
前端同學一開始只需要執行以下命令:
$ npm install // 安裝依賴$ npm start -- RDName // 開始開發
npm start 這條命令實際執行的是:
$ cross-env HOT=true fis3 release RDName -c && npm run dev
具體解釋一下:
- 首先通過cross-env設置一個環境變數HOT為true.
- 進行fis3 release的過程中,如果檢測到這個變數有效,會通過一個fis的後處理器 fis-postprocessor-smarty-hmr 對smarty模板文件(*.tpl)進行預處理,往smarty模板中注入一段js,指向本地dev-server。之後執行release, 把修改後的smarty模板推到RD開發機。(這裡無需開啟fis3的—watch模式)。
- 啟動一個本地的webpack dev-server.
- 訪問RD開發機對應的前端頁面,此時頁面會和本地的webpack-dev-server進行連接。
- 本地的IDE修改了代碼後,webpack dev-server自動刷新,在RD開發機的前端頁面就實現了hotReload。
原理如上圖,一言蔽之:
RD開發機頁面實際是套著smarty的殼,載入webpack bundle的「心」;
它會載入本地的webpack-dev-server的資源,實現hotReload。
當然,這裡需要做一些簡單配置,可以讓fis和webpack在這種環境下協同工作。
- fis-conf.js裡面需要對fis-postprocessor-smarty-hmr這個插件做一些簡單配置,如配置tpl需要載入的bundle, 是否生效等。具體可以看這裡 。
- 本地的dev-server基於express和webpack-dev-middleware。要把本地的dev-client(基於webpack-hot-middleware)加入webpack entry, 並強制指向localhost;dev環境下的webpack publicPath也需要強制指向localhost; 否則在RD開發機頁面上不會訪問localhost://__hmr這個hotReload服務。
- 顯然,開發機頁面上訪問localhost服務是跨域的,需要將express開啟CORS。
- webpack無需做uglify等壓縮操作,統一交給fis;
- fis無需做babel的parse, 統一交給webpack。
這裡一些更細節的配置不再贅述,大體上,webpack的配置都可以在各個前端業務模塊中通用,僅有的區別是根據項目不同對 webpack entry 和 fis-postprocessor-smarty-hmr 作一些簡單的差異化配置,即可啟用。
驗證階段
前面的開發階段,RD開發機頁面資源依賴於本地的dev-server。也許有同學會問,如果我把自己電腦一關,別人不就訪問不了這個頁面?的確是這樣的。
在這種情況下,前端同學只要執行以下命令:
$ npm run release:dist -- RDName
這條命令實際做的事:
$ npm run build && fis3 release RDName
實際先執行了webpack bundle的打包,再執行fis release。
這裡與前面一階段的不同就在於沒有設置環境變數HOT, 那麼相應的smarty模板就不會走 fis-postprocessor-smarty-hmr的後處理,這樣開發機頁面載入的就是已經推到開發機的資源。
我們避免在webpack裡面執行uglify, 也是為了把這種耗時操作留到真正需要時再做(也就是上線階段)。
提測/上線階段
提測/上線階段,在外賣的環境下就是配合持續集成平台做相應的腳本配置,打包出ODP需要的資源。
在這裡只需要在持續集成的腳本文件ci.yml中加入相關腳本即可:
Image: type : wm-fe-centos6u5BeforeBuild: script : ./beforeBuild.shBuild: script : ./build.sh
beforeBuild.sh只需執行:
$ npm install$ npm run build
讓webpack切換到生產環境配置,打包出bundle文件,接著執行build.sh,由fis接管進行壓縮、打包等操作,產出符合ODP規範的包。
這樣整個前端開發流程就結束了,fis和webpack分工明確,執行得很好。
總結
前文提到的方案,有以下優勢:
- 在後端模板的場景下,解決了node_module的包管理問題;
- 享受hotReload等其他webpack帶來的便利,提高開發效率的同時,可以緊貼社區腳步,方便地實踐與運用新技術;
- fis和webpack分工合作,可以充分發揮兩大工程化方案的價值;
- 方案同時可兼容fis3和fis-plus,便於現有項目改造。
目前存在的小問題是,需要同時做fis和webpack的配置,實際需要的配置點雖不多,但是依賴純手工配置,略顯繁瑣。但是我們認為在項目初始時,花一些時間來針對項目做具體的配置是「磨刀不誤砍柴工」的,畢竟配置完之後,後續就可以延用配置成果了。
而且實際上,webpack的大多數配置可以基本固定,後續我們也將產出一些命令行工具來方便地進行初始化fis+webpack後端模板工程化方案的配置(譬如將webpack配置抽象在cli端,使項目文件更加簡潔)。
以上,希望本文能對在後端模板場景下的前端開發同學提供一些思路和探討。共勉~
推薦閱讀:
※webpack源碼學習系列之一:如何實現一個簡單的webpack
※webpack打包之 緩存
※使用 webpack-dashboard 讓開發伺服器運行得更炫酷
※重溫 Webpack, Babel 和 React
※使用vue-cli,還有必要學習webpack嗎?