小型 Web 頁項目打包優化方案

来源:http://www.cnblogs.com/zhuanzhuanfe/archive/2017/07/24/7230365.html
-Advertisement-
Play Games

背景 目前團隊中新的 Web 項目基本都採用了 Vue 或 React ,加上 RN,這些都屬於比較重量級的框架,然而對於小型 Web 頁面,又顯得過大。早期的一些項目則使用了較原始的 HTML 頁面構建技術,但業務邏輯基本無法復用。 近半年做過幾個小型 Web 頁面,在不斷學習前端知識的同時,也在 ...


背景

  目前團隊中新的 Web 項目基本都採用了 Vue 或 React ,加上 RN,這些都屬於比較重量級的框架,然而對於小型 Web 頁面,又顯得過大。早期的一些項目則使用了較原始的 HTML 頁面構建技術,但業務邏輯基本無法復用。
  近半年做過幾個小型 Web 頁面,在不斷學習前端知識的同時,也在重構並摸索小型 Web 項目可能的更好解決方案。本文則對之前的工作進行一次整體描述。

 

目標和定位

  單論小型 Web 頁面,其相對於 Vue/React 等項目最大不同是不需要支持 SPA 這種比較重的形式,以 MVP(Minimum Viable Product) 為原則,小頁面只要滿足需求,做到夠用即可。所以在對現有原始 Web 頁面進行重構時,會將以下兩個方面作為最高優先順序:

  1. 不斷提高項目的重用性、可維護性;

  2. 不斷提高前端性能,這裡主要是載入性能;

  對於第一點,組件化代碼結構是當前最可行的思路;對於第二點,在做到第一點的前提下,極少的第三方依賴,良好的打包方法,是必須要做到的。

 

項目結構演化歷程

  本文所描述的小型 Web 頁項目結構和打包方法是經過若幹次項目重構才得到的。

 

第一版 

  第一版項目基本上以最原始的 HTML+JS+CSS 為基準。為了讓項目代碼更好維護,首先考慮到的是有兩個點需要做:

  1. 使頁面內容具有維護性,需要採用 JS 模板;

  2. 由於業務複雜,分工較細,介面繁多,需要將數據接入層 DAL(Data Access Layer) 單獨分離出來;

  對於第一個問題,最後選擇 Mustache 庫。原因是它語法極簡,容易學習,同時該類型語法有廣大用戶群體,當然同樣流行的還有 underscore/ejs 類型的模板語法。為了保證內容頁面的無邏輯性和簡單,故 Mustache 的高級版 Handlebars 未被使用。
  第二個問題是對公司業務和項目代碼有所瞭解後所下的結論。相當於是對現有代碼的重構,主要目的是進行職責分離,將複雜多變的介面隔離出來,讓剩下的代碼專心解決業務問題。
  開始敲代碼後,才發現另一個比較嚴重的問題:我需要把管理內容模板的代碼單獨分離出來,使其不會影響主要的業務邏輯,於是想到了 MVP(Model View Presenter) 模式。簡單講,這個就是 MVVM 模式去掉 View-ViewModel 雙向數據綁定後的一個弱化版。如下圖所示:

 

 

 在小型 Web 頁面中,一般是沒有 Model 層的。頁面中的 Presenter 部分只負責通過參數控制界面的渲染,並以組件的方式對外公開 View 層事件。按照這個思路,第一版項目結構就基本出來了,見下圖:

 

 

 

引入 Webpack 2+

 第一版項目結構已經足以應付小型 Web 頁面的需求了,同時也不會帶來較多的複雜性。但是原始 Web 頁面天生就不利於模塊化開發,同時存在一個根本性問題:

  1. 代碼解耦會使項目文件結構清晰,職責分離,有利於維護;

  2. 打包結果需要將相關代碼壓縮到單個文件中,便於提高載入性能;

  在 Web 頁面開發中,這兩者形成一個悖論。所以需要引入一個打包機制,將項目代碼和打包文件進行解耦。年老的 Gulp 和 Grunt 就不看了,現存項目中用的較多的是 FIS3 和 Webpack 1.x。前者國產,使用起來也非常方便,後者難度高一些,但是跟其他國外開源項目一樣,他們總能把一個軟體的 50% —— 文檔做的很好。(其實還有很多可以吐槽的地方,但看了之後感覺相對更踏實)
  在看了 Webpack 2.x 的文檔之後,基本就確定使用該打包機制了,它有如下優點讓人欲罷不能:

  1. 原生支持 import 語法。這樣就徹底擺脫文件結構不好管理的問題了,面向對象、模塊化什麼的統統都可以引進來,終於可以舒舒服服寫代碼了;

  2. 支持 Tree Shaking。本來這是 rollup 打包機制獨有的特點,現在 Webpack 也有了;

  3. Webpack 的配置文件雖然複雜,但瞭解之後再配合插件機制,會發現它潛力很大,使用也相當靈活;

  在引入 Webpack 2.x 後,不同功能均以單個文件的形式進行分開,各模塊之間介面也變得非常明確,但還有待改進。

 

參考 Vue 項目結構

  加入打包機制後,JavaScript 文件已經解耦的不錯了,但是模板還都放在了首頁中,樣式也都放在了一個文件中,依賴關係混亂,不方便管理。改良它的一個好方法是參考其他優秀項目,比如 Vue 就有一套很好的項目組織結構,直接借鑒就行了。新的項目主要變化如下:

  1. 真正將組件分離出來。組件內容採用 Mustache 模板,樣式採用 Less 語法,JS 部分則控制組件的渲染邏輯,儘量不關聯業務邏輯,三者合一就相當於一個 .vue 文件了。通過修改 Webpack 配置或使用合適的插件,該方式可以同樣支持其他模板和 CSS 語法,比如 ejs 或者 SCSS

  2. 選擇支持多頁面入口,而沒有採用路由功能。這樣可以簡化 SPA 中複雜的 URL 結構,同時打包結果也不用附帶路由邏輯。這樣還有一個好處是後期引入簡單版的 SSR 也會很方便,路由就是 nginx 的事兒了;

     


  對於該部分項目結構的詳細描述直接看下文。結構圖如下:

       需要說明的是,圖中的 State Store 其實目前是沒有的,放在這裡主要是為了好看 :)。後期如果把 vuex/MobX/Redux 之類的加進去了,那就完整了,目前因為業務邏輯很簡單,狀態什麼的暴力解決就行了。而 app.js 則處理項目中公共的業務邏輯,讓頁面入口解脫出來專心處理內容。

 

 

項目結構描述

 

目錄結構

項目目錄結構如下:

----build                           # Webpack 配置文件
        ...
----src
--------assets                      # 資源文件
--------components ------------GoodsInfo               # 商品信息組件                GoodsInfo.mst       # 組件模板,採用 Mustache 語法                GoodsInfo.js        # 組件渲染和操作邏輯。一般業務無關                GoodsInfo.less      # 組件樣式
------------RiskPromt                ... ------------ShareHeader                ... ------------SharePanel                ...            utils.js                # 業務無關,視圖層相關的輔助方法集合
--------dal                         # 數據接入層            index.js                # 入口文件。集中管理請求介面和偽數據            getInfoById.js          # 介面請求實現            getInfoById.json        # 介面返回偽數據,在 index.js 中可生成 mock 方法
--------Main                        # 預設頁面入口            Main.html               # 頁面模板            Main.js                 # 頁面業務邏輯            Main.less               # 頁面樣式
--------MainBanner                  # 帶有底部 Banner 的頁面入口            ...        app.js                      # 抽取多頁面共有的業務邏輯,比如分享功能的具體實現        common.js                   # 應用級的輔助方法集合        common.less    package.json    README.md

 

第三方依賴

在 Webpack 2+ 的幫助下,項目選用瞭如下開源第三方庫作為基礎依賴:

  1. es6-promise:採用 Promise 的方式可以使代碼更清晰更好維護;

  2. axios:Vue 官方推薦的 vue-resource 替代品;

  3. mustache:項目所用的模板庫

另外還使用了團隊維護的 SDK:

  1. @zz/zz-jssdk:提供 Web 頁面和轉轉 App 客戶端的交互介面

  2. @zz/perf:性能統計工具

     

     


  由於 axios 官方堅持不集成非標準的 jsonp 請求,對於現存部分只支持 jsonp 請求的介面,還需要引入 jsonp 第三方開源庫。
  以上是項目文件依賴。開發依賴中,所用的第三方庫基本都是 Webpack 相關,包括 Less 文件的解析模塊。項目沒有引入 babel-polyfill 進行 ES6 語法的開發,因為容易產生不必要的額外打包代碼。

 

文件載入規則配置

  在 Webpack 的語義下,所有的項目文件都是一種資源,供 JavaScript 使用,所以處理任何資源時,只要配置好合適的 loader 即可。該部分則對項目中不同類型文件的載入和解析規則配置進行了簡要描述。這裡不會講解 Webpack 配置細節,相關內容請查看官方文檔。

  • 資源和圖片

   對於一般資源文件的載入,採用 file-loader 即可。對於圖片文件,採用推薦的 url-loader。該載入器有一個選項是,如果圖片小於指定值,會將其轉化為 DataUri 嵌入到打包文件中,以減少額外 HTTP 請求,項目設置指定值為常用的 10K。規則如下:

 

 

 

  • 樣式文件

        項目中樣式文件預設採用 Less,主要用到該庫的兩個特性:

  1. 可以方便的使用 CSS 變數,典型的比如定義通用像素大小;

  2. 層次化的樣式描述方式;

     

  
     Webpack 配置同時保留了 css 文件的載入能力,後期還可以加入對 SCSS 文件支持。規則如下:

 

 

        同一個項目中,由於 CSS/LESS/SCSS 文件之間具有依賴關係,所以強烈推薦採用同一種技術實現。對於單個組件,不大可能像 Vue 一樣寫個 Webpack Loader 支持 .vue 類型的組件格式。樣式文件的載入需要在對應的 .js 文件中顯式引入 .less 文件,比如:

 

 

 

  • 模板文件

   項目模板預設採用 Mustache,在 Webpack 的支持下,模板內容被單獨放在一個文件中,並以 .mst 作為自定義尾碼,文件內容依然是 HTML 格式,只是根標簽為 <template>。Webpack 中選用 html-loader 對其進行解析,規則如下:

 

 

 

  對於 Mustache 模板的自動解析和載入,網上有開源的 mustache-loader 實現,但其關註度實在太低,而 html-loader 足以達到所需功能:

  1. 載入 .mst 文件,並壓縮內容;

  2. 將文件中 img:src 等相對路徑屬性自動替換為絕對目標地址;

  對於其他模板語言同樣可以使用這種方法,就可以在項目中靈活的使用不同的模板庫了。不過需要註意的是,同一個項目中最好只使用一種模板語言,方便管理,同時不會增加打包文件大小。
  將 .mst 模板載入到頁面中和 .less 方法差不多。在對應的 .js 文件中顯式引入,然後用 extractTemplate 方法提取出模板內容即可:

 

 

 

 這種顯式引入的方式有一個好處是,可以手動控制不同的模板和樣式。在實際產品需求中,內容和樣式改變是很頻繁的,而功能邏輯的變化相對要慢一些,這樣通過 js 引用不同版本的模板和樣式就會比較靈活。如果能把這一套管理機制抽象出來單獨進行配置也是很不錯的。

  • 頁面文件

  頁面文件在 Webpack 中也是以模板的角色存在的,解析方式和模板一樣,規則見上文。由於是頁面入口文件,在 Webpack 中還需要使用 HtmlWebpackPlugin 插件進行配置。如下配置中,項目存在兩個不同的頁面入口,所以需要兩個 HtmlWebpackPlugin 實例:

 

 

      由於用戶每次進入 Web 頁面都會載入首頁,所以首頁越小越節省流量。參考 Vue 項目的 index.html 就會發現裡面基本只有一個骨架,具體內容都在組件中。但項目配置本身不會對這點進行假設,所以即使在首頁中寫入所有內容也是可行的。

 

打包

  項目的主要打包配置前文已經介紹差不多了,其他具體配置參看官方文檔即可。採用該項目結構的最後打包結果,所有部署文件包括圖片加起來沒有超過 130K。在瀏覽器中,因為 gzip 的原因,全頁面載入網路流量不到 70K。

 

數據接入層

  前文已經提到過,把數據請求單獨作為一個層主要是為了分離出複雜多變的數據請求介面,還有一個好處是介面 mock 數據也可以在這裡統一處理。

介面封裝

  一個項目中可能在很多地方都會請求同一個介面。對於單個介面請求,可能有不同方法,比如用 ajax、fetch、jsonp、axios 甚至 jQuery 庫;有的是 GET,有的是 POST;有的還需要帶 cookie,其他卻不需要;返回數據的格式也許還不是統一的。而 JavaScript 邏輯只關心輸入和輸出,把這些請求細節都放在另外一個地方單獨維護,會使主要業務邏輯更加簡潔。在項目中使用時,只需要以 Promise 的形式調用方法即可。介面封裝的示例代碼如下:

 

 

 

封裝 mock 數據

  前後端協同開發時,需要首先定好介面,給出 mock 數據示例。所以在 DAL 層把 mock 數據封裝好,會節省很多工作。在項目中會將 mock 數據直接保存成 .json 格式的文件,然後在 DAL 入口文件中通過 import 導入,再使用一個工廠方法來對外提供 mock 方法,即可使用 mock 數據了。下麵是入口文件中相關代碼:

 

通過 DAL 使用介面

  有了 DAL 層對各請求介面的聚合,在其他地方使用就比較簡單了,直接上代碼:

 

組件化開發

小型 Web 頁面的組件和 .vue 文件結構類似,只是分成了三個文件:

  1. 樣式。內容和使用方式是基本一樣的;

  2. 模板。後者 Vue 有自己的模板語法,前者則用的 Mustache,也可支持其他模板。如果 Vue 的模板載入器單獨分離出來,那理論上也是可以拿過來使用的;

  3. 控制邏輯。JS 邏輯部分則有些不一樣,Vue 框架有著自己獨特完美的雙向綁定機制,其介面和生命周期也是圍繞它來設計的(這裡只針對 .vue 文件進行討論,類 React 使用方式很大程度是為了方便拉取用戶而設定)。小型 Web 頁面因為簡單,所以重心都放在了組件初始化和渲染上;

組件在小型 Web 頁面中定位是很明確的,即只針對頁面呈現和交互,所以對外介面的設計也不複雜。如果組件採用的是 MVC 模式,那就很難討論,因為 Controller 本身就是“老大”,可能有很多行為。Presenter 和 ViewModel 則相對簡單,它們的區別隻是內在機制不同,對外是行為是差不多的。這裡不考慮大型 Web 頁面,小型 Web 頁中組件的介面預設就兩種:接受純數據參數(props);對外公佈事件介面。相比於更高級的 Vue,少了一個 Slot 插槽功能。

使用組件

  使用組件的方式很直接,看代碼:

 

組件中一個 init 方法並不能搞定全部需求,因為項目中 init 方法不僅包含了組件渲染邏輯,還有事件綁定邏輯。當組件數據內容更新時,還需要抽取出一個 render 或 update 方法單獨進行調用來更新界面。這不像 Vue 自帶雙向數據綁定神器,所以要麻煩點。
  使用組件提供的事件也很簡單,代碼如下:

 

這裡事件句柄的參數採用了 (Object data, Event e) 的形式。其中 data 表示事件來源,它可以是被點擊對象的 ViewModel,或者簡單點,直接是被點擊對象所代表的的原始數據;e 則是 HTML 的事件參數。

組件的參數處理和渲染

  組件內部綁定到具體的模板前文已經示例說明過了。在渲染組件內容時,還需要處理參數內容,並將其渲染到頁面指定地方。這裡直接上代碼:

 

 

在構造器中,首先定義 props 參數的格式,並給上預設值。在 init 方法中,則將 data 中的參數賦值給 props,這裡一般是會有數據轉化處理邏輯。
  最後直接進行組件渲染。可以發現,如果想要使用其他模板引擎,是很容易替換的。如果採用 SSR 服務端渲染組件,那可以各種模板庫全放進來,一個工廠方法就可以進行自動化處理。
  組件的參數被取名為 props,完全是仿造 Vue/React。因為它們的功能和定位基本是一樣的,而且官方推薦的最佳實踐這裡也基本都推薦。具體這樣做的幾點思路如下:

  1. 小項目做不到 Vue/React 的參數驗證功能,但顯式表示 props 參數有自描述文檔的作用,需要哪些參數及其類型一目瞭然;

  2. 構造器中同時給出了 props 預設值,無參數時組件有預設展示形式;

  3. 參數只有一個 data 對象。Vue 推薦參數都用基本類型,但內容龐大時,屬性繁多,分割成更小組件也不會減少多少使用的複雜性;

  4. props 中的每一個屬性不能是對象,只能是 IntegerStringBooleanArray 等基本類型;

對外公佈事件

  將事件的觸發封裝到組件中也是為了減少業務的複雜性。很多 Web 項目中都是直接操作頁面內容,用戶交互、內容處理、業務邏輯都耦合在了一起,這裡組件將用戶交互封裝起來,同時對外提供事件介面。代碼如下:

 

組件內部保存一個事件回調句柄 clickCallback,組件初始化時對用戶點擊事件進行數據綁定,並觸發這個回調。

結論

  本文簡單描述了小型 Web 頁面的定位,通過對小型 Web 頁面的摸索和演化解釋了當前項目結構的設計思路,並對其細節進行了詳細描述,重點介紹了數據接入層和組件化開發。
  當前的項目並不是最終形態,而只是一個 α 版本的雛形,還有很多地方值得改善:

  1. 針對首屏時間進行優化,比如支持 SSR;

  2. 繼續改善打包部署方案,靈活支持多頁面部署,達到或接近離線應用的效果;

  3. 一些好的 ES6 語法很值得支持,需要找到一個方法在打包層面上漸進式的引入特定語法;

  4. 基於 Promise 的語法值得大面積採用,這是代碼層面需要考慮的;

  5. Webpack 挺好,但還不夠好,希望插件能更成熟更豐富;

  可能還有很多點沒考慮到,不過實際需求永遠是最高優先順序。只要不斷的重構和改善,軟體就會一直有生命力~

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 提起路由,首先想到的就是 ASPNET MVC 裡面的路由系統 通過事先定義一組路由規則,程式運行時就能自動根據我們輸入的URL來返回相對應的頁面。前端中的路由與之類似,前端中的路由是根據你定義的路由規則來渲染不同的頁面/組件,同時也會更新地址欄的URL。本篇文章要介紹的是React中經常使用到的路 ...
  • MVC是什麼? MVC是一種架構模式,它將應用抽象為3個部分:模型(數據)、視圖、控制器(分發器)。 本文將用一個經典的例子todoList來展開(代碼在最後)。 一個事件發生的過程(通信單向流動): 1、用戶在視圖 V 上與應用程式交互 2、控制器 C 觸發相應的事件,要求模型 M 改變狀態(讀寫 ...
  • 一、document.formName.item(”itemName”) 問題 問題說明:IE下,可以使用 document.formName.item(”itemName”) 或 document.formName.elements ["elementName"];Firefox 下,只能使用do ...
  • JS數組的基礎操作代碼: <script type="text/javascript"> 數組的三種定義 var arr1 = new Array(); var arr3 = Array(1,2,3.5,4,'5',false); var arr2 = [1,2,3,4,'5',"aa",true] ...
  • 開啟第一版本的迭代設計,互動朋友圈API設計,上篇文章公佈了我們的初稿原型圖設計。 該APP類似微信朋友圈,添加好用功能類似微博添加好友的那種關註與被關註的形式。運營形式類似於快手! 互動朋友圈API設計 一、登錄 請求地址:http://192.168.2.8020/api/Login 請求方式: ...
  • 一:引入bootstrap框架 昨天一直被bootstrap柵格系統折磨。 why? 我本來想一邊碼字,一邊學習柵格佈局的。but不成功。這時我頭腦已經昏了。 下午,我查看了bootstrap的官網,帶著我的問題:究竟怎麼使用bootstrap的框架呢? 發現問題一:我原先外部引入的bootstra ...
  • [1]概述 [2]大括弧表示 [3]字元編解碼 [4]for...of [5]normalize() [6]U修飾符 ...
  • 開始編碼工作也有段時間了,想想沒有留下點什麼,有點遺憾。學到的一些經驗,寫寫,分享一下。也給自己整理一下。 今天分享一下,在原有的日期上添加天數輸出添加後的日期。開始做的時候,簡單的思路是,直接用new Date(),得到的本地時間再在new Date().getDate();再加上對應的天數。 這 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...