實現一個頁面功能老是須要 JavaScript、CSS 和 Template 三種語言相互組織,因此咱們真正須要的是一種能夠將 JavaScript、CSS 和 Template 同時都考慮進去的模塊化方案。javascript
不少主流的模塊化解決方案經過 JavaScript 運行時來支持「匿名閉包」、「依賴分析」和「模塊加載」等功能,例如「依賴分析」須要在 JavaScript 運行時經過正則匹配到模塊的依賴關係,而後順着依賴鏈(也就是順着模塊聲明的依賴層層進入,直到沒有依賴爲止)把全部須要加載的模塊按順序一一加載完畢, 當模塊不少、依賴關係複雜的狀況下會嚴重影響頁面性能。css
傳統的模塊化方案更多的考慮是如何將代碼進行拆分,可是當咱們部署上線的時候須要將靜態資源進行合併(打包),這個時候會發現困難重重,每一個文件裏 只能有一個模塊,由於模塊使用的是「匿名定義」,通過一番研究,咱們會發現一些解決方案,不管是「 combo 插件」仍是「 flush 插件」,都須要咱們修改模塊化調用的代碼,這無疑是雪上加霜,開發者不只僅須要在本地開發關注模塊化的拆分,在調用的時候還須要關注在一個請求裏面加載哪 些模塊比較合適,模塊化的初衷是爲了提升開發效率、下降維護成本,但咱們發現這樣的模塊化方案實際上並無下降維護成本,某種程度上來講使得整個項目更加 複雜了。html
首先咱們來看一下一個 web 項目是如何經過「一體化」的模塊化方案來劃分目錄結構:前端
前端模塊(widget),是能獨立提供功能且可以複用的模塊化代碼,根據複用的方式不一樣分爲 Template 模塊、JS 模塊、CSS 模塊三種類型,CSS 組件,通常來講,CSS 模塊是最簡單的模塊,它只涉及 CSS 代碼與 HTML 代碼; JS 模塊,稍爲複雜,涉及 JS 代碼,CSS 代碼和 HTML 代碼。通常,JS 組件能夠封裝 CSS 組件的代碼; Template 模塊,涉及代碼最多,能夠綜合處理 HTML、JavaScript、CSS 等各類模塊化資源,通常狀況,Template 會將 JS 資源封裝成私有 JS 模塊、CSS 資源封裝成本身的私有 CSS 模塊。下面咱們來一一介紹這幾種模塊的模塊化方案。java
咱們能夠將任何一段可複用的模板代碼放到一個 smarty 文件中,這樣就能夠定義一個模板模塊。在 widget 目錄下的 smarty 模板(本文僅以 Smarty 模板爲例)即爲模板模塊,例如 common 子系統的 widget/nav/ 目錄jquery
├── nav.css
├── nav.js
└── nav.tpl
下 nav.tpl 內容以下:web
<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 資源的模板模塊,shell
// 調用模塊的路徑爲 子系統名稱:模板在 widget 目錄下的路勁
{widget name="common:widget/nav/nav.tpl" }
這個模板模塊(nav)目錄下有與模板同名的 JS、CSS 文件,在模板被執行渲染時這些資源會被自動加載。如上所示,定義 template 模塊的時候,只須要將 template 所依賴的 JS 模塊、CSS 模塊存放在同一目錄(默認 JavaScript 模塊、CSS 模塊與 Template 模塊同名)下便可,調用者調用 Template 模塊只須要寫一行代碼便可,不須要關注所調用的 template 模塊所依賴的靜態資源,模板模塊會幫助咱們自動處理依賴關係以及資源加載。後端
上面咱們介紹了一個模板模塊是如何定義、調用以及處理依賴的,接下來咱們來介紹一下模板模塊所依賴的 JavaScript 模塊是如何來處理模塊交互的。咱們能夠將任何一段可複用的 JavaScript 代碼放到一個 JS 文件中,這樣就能夠定義爲一個 JavaScript 類型的模塊,咱們無須關心「 define 」閉包的問題,咱們能夠得到「 CommonJS 」同樣的開發體驗,下面是 nav.js 中的源碼.bash
// 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 用於處理首屏外的按需模塊。
在模板模塊中以及 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/