前端工程模塊化——以一個php項目為例


實現一個頁面功能總是需要 JavaScript、CSS 和 Template 三種語言相互組織,所以我們真正需要的是一種可以將 JavaScript、CSS 和 Template 同時都考慮進去的模塊化方案。

前端模塊化帶來的性能問題

很多主流的模塊化解決方案通過 JavaScript 運行時來支持“匿名閉包”、“依賴分析”和“模塊加載”等功能,例如“依賴分析”需要在 JavaScript 運行時通過正則匹配到模塊的依賴關系,然后順着依賴鏈(也就是順着模塊聲明的依賴層層進入,直到沒有依賴為止)把所有需要加載的模塊按順序一一加載完畢, 當模塊很多、依賴關系復雜的情況下會嚴重影響頁面性能。

模塊化為打包部署帶來的極大不便

傳統的模塊化方案更多的考慮是如何將代碼進行拆分,但是當我們部署上線的時候需要將靜態資源進行合並(打包),這個時候會發現困難重重,每個文件里 只能有一個模塊,因為模塊使用的是“匿名定義”,經過一番研究,我們會發現一些解決方案,無論是“ combo 插件”還是“ flush 插件”,都需要我們修改模塊化調用的代碼,這無疑是雪上加霜,開發者不僅僅需要在本地開發關注模塊化的拆分,在調用的時候還需要關注在一個請求里面加載哪 些模塊比較合適,模塊化的初衷是為了提高開發效率、降低維護成本,但我們發現這樣的模塊化方案實際上並沒有降低維護成本,某種程度上來說使得整個項目更加 復雜了。

首先我們來看一下一個 web 項目是如何通過“一體化”的模塊化方案來划分目錄結構:

image1

  • 站點(site):一般指能獨立提供服務,具有單獨二級域名的產品線。如旅游產品線或者特大站點的子站點(lv.baidu.com)。
  • 子系統(module):具有較清晰業務邏輯關系的功能業務集合,一般也叫系統子模塊,多個子系統構成一個站點。子系統(module)包括 兩類: common 子系統, 為其他業務子系統提供規范、資源復用的通用模塊;業務子系統:,根據業務、URI 等將站點進行划分的子系統站點。
  • 頁面(page): 具有獨立 URL 的輸出內容,多個頁面一般可組成子系統。
  • 模塊(widget):能獨立提供功能且能夠復用的模塊化代碼,根據復用的方式不同分為 Template 模塊、JS 模塊、CSS 模塊三種類型。
  • 靜態資源(static):非模塊化資源目錄,包括模板頁面引用的靜態資源和其他靜態資源(favicon,crossdomain.xml 等)。

前端模塊(widget),是能獨立提供功能且能夠復用的模塊化代碼,根據復用的方式不同分為 Template 模塊、JS 模塊、CSS 模塊三種類型,CSS 組件,一般來說,CSS 模塊是最簡單的模塊,它只涉及 CSS 代碼與 HTML 代碼; JS 模塊,稍為復雜,涉及 JS 代碼,CSS 代碼和 HTML 代碼。一般,JS 組件可以封裝 CSS 組件的代碼; Template 模塊,涉及代碼最多,可以綜合處理 HTML、JavaScript、CSS 等各種模塊化資源,一般情況,Template 會將 JS 資源封裝成私有 JS 模塊、CSS 資源封裝成自己的私有 CSS 模塊。下面我們來一一介紹這幾種模塊的模塊化方案。

模板模塊

我們可以將任何一段可復用的模板代碼放到一個 smarty 文件中,這樣就可以定義一個模板模塊。在 widget 目錄下的 smarty 模板(本文僅以 Smarty 模板為例)即為模板模塊,例如 common 子系統的 widget/nav/ 目錄

├── nav.css
├── nav.js
└── nav.tpl

下 nav.tpl 內容如下:

<nav id="nav" class="navigation" role="navigation"> <ul> <%foreach $data as $doc%> <li class="active"> <a href="#section-{$doc@index}"> <i class="icon-{$doc.icon} icon-white"></i><span>{$doc.title}</span> </a> </li> <%/foreach%> </ul> </nav> 

然后,我們只需要一行代碼就可以調用這個包含 smarty、JS、CSS 資源的模板模塊,

// 調用模塊的路徑為 子系統名稱:模板在 widget 目錄下的路勁
{widget name="common:widget/nav/nav.tpl" }

這個模板模塊(nav)目錄下有與模板同名的 JS、CSS 文件,在模板被執行渲染時這些資源會被自動加載。如上所示,定義 template 模塊的時候,只需要將 template 所依賴的 JS 模塊、CSS 模塊存放在同一目錄(默認 JavaScript 模塊、CSS 模塊與 Template 模塊同名)下即可,調用者調用 Template 模塊只需要寫一行代碼即可,不需要關注所調用的 template 模塊所依賴的靜態資源,模板模塊會幫助我們自動處理依賴關系以及資源加載。

JavaScript 模塊

上面我們介紹了一個模板模塊是如何定義、調用以及處理依賴的,接下來我們來介紹一下模板模塊所依賴的 JavaScript 模塊是如何來處理模塊交互的。我們可以將任何一段可復用的 JavaScript 代碼放到一個 JS 文件中,這樣就可以定義為一個 JavaScript 類型的模塊,我們無須關心“ define ”閉包的問題,我們可以獲得“ CommonJS ”一樣的開發體驗,下面是 nav.js 中的源碼.

// common/widget/nav/nav.js var $ = require('common:widget/jquery/jquery.js'); exports.init = function() { ... }; 

我們可以通過 require、require.async 的方式在任何一個地方(包括 html、JavaScript 模塊內部)來調用我們需要的 JavaScript 類型模塊,require 提供的是一種類似於后端語言的同步調用方式,調用的時候默認所需要的模塊都已經加載完成,解決方案會負責完成靜態資源的加載。require.async 提供的是一種異步加載方式,主要用來滿足“按需加載”的場景,在 require.async 被執行的時候才去加載所需要的模塊,當模塊加載回來會執行相應的回調函數,語法如下:

// 模塊名: 文件所在 widget 中路徑 require.async(["common:widget/menu/menu.js"], function( menu ) { menu.init(); }); 

一般 require 用於處理頁面首屏所需要的模塊,require.async 用於處理首屏外的按需模塊。

CSS 模塊

在模板模塊中以及 JS 模塊中對應同名的 CSS 模塊會自動與模板模塊、JS 模塊添加依賴關系,進行加載管理,用戶不需要顯示進行調用加載。那么如何在一個 CSS 模塊中聲明對另一個 CSS 模塊的依賴關系呢,我們可以通過在注釋中的@require 字段標記的依賴關系,這些分析處理對 html 的 style 標簽內容同樣有效,

/** * demo.css * @require reset.css */ 

非模塊化資源

在實際開發過程中可能存在一些不適合做模塊化的靜態資源,那么我們依然可以通過聲明依賴關系來托管給靜態資源管理系統來統一管理和加載,

{require name="home:static/index/index.css" }

如果通過如上語法可以在頁面聲明對一個非模塊化資源的依賴,在頁面運行時可以自動加載相關資源。

項目實例

下面我們來看一下在一個實際項目中,如果在通過頁面來調用各種類型的 widget,首先是目錄結構:

├── common
│   ├── fis-conf.js
│   ├── page
│   ├── plugin
│   ├── static
│   └── widget
└── photo
    ├── fis-conf.js
    ├── output
    ├── page
    ├── static
    ├── test └── widget 

我們有兩個子系統,一個 common 子系統(用作通用),一個業務子系統,page 目錄用來存放頁面,widget 目錄用來存放各種類型的模塊,static 用於存放非模塊化的靜態資源,首先我們來看一下 photo/page/index.tpl 頁面的源碼,

{extends file="common/page/layout/layout.tpl"}
{block name="main"}
    {require name="photo:static/index/index.css"}
    {require name="photo:static/index/index.js"}
    <h3>demo 1</h3> <button id="btn">Button</button> {script type="text/javascript"} // 同步調用 jquery var $ = require('common:widget/jquery/jquery.js'); $('#btn').click(function() { // 異步調用 respClick 模塊 require.async(['/widget/ui/respClick/respClick.js'], function() { respClick.hello(); }); }); {/script} // 調用 renderBox 模塊 {widget name="photo:widget/renderBox/renderBox.tpl"} {/block} 

第一處代碼是對非模塊化資源的調用方式;第二處是用 require 的方式調用一個 JavaScript 模塊;第三處是通過 require.async 通過異步的方式來調用一個 JavaScript 模塊;最后一處是通過 widget 語法來調用一個模板模塊。 respclick 模塊的源碼如下:

exports.hello = function() { alert('hello world'); }; 

renderBox 模板模塊的目錄結構如下:

└── widget
    └── renderBox
        ├── renderBox.css
        ├── renderBox.js
        ├── renderBox.tpl
        └── shell.jpeg

雖然 renderBox 下面包括 renderBox.js、renderBox.js、renderBox.tpl 等多種模塊,我們再調用的時候只需要一行代碼就可以了,並不需要關注內部的依賴,以及各種模塊的初始化問題。

 

fis開源項目前端工程模塊化: http://fex.baidu.com/blog/2014/03/fis-module/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM