移動Web前端模版:Qing
什么是Qing?
Qing是一套基礎開發模版,來源于我們在手機與PC端上的大量工程實踐。Qing所提供不是冷冰冰的文件, 而是一套Web前端解決方案,所以Qing不只是關注項目的初始狀態,而是整體的工作流程, 這是Qing與現有開源的開發模版顯著差異的一點。Qing的體驗必須是高效且愉悅的,拒絕繁瑣與重復。 其足夠的Qing量,只需30分鐘內即可掌握最先進的Web開發技能。
以下是Qing所基于的開發理念:
-
移動端優先,兼容PC端
-
向前看齊,基于ES5開發
-
模塊化Web開發過程
-
自動構建與部署集成, 基于Mod.js工具
基于未來趨勢的開發理念,Qing旨在提供工程化方案。
平臺與瀏覽器版本兼容:
-
iOS 4.0+
-
Android 2.2+
-
IE 6+
-
Chrome
-
Firefox
-
Safari
開始使用
可以通過以下任意一種方式開始使用Qing模版:
-
下載最新Qing模版包, 解壓至目標目錄
-
如果已安裝git,可使用git clone源碼至目標目錄:
$ git clone https://github.com/AlloyTeam/Qing.git
-
如果已安裝了Mod.js, 推薦在目標目錄執行:
$ m download AlloyTeam/Qing
第一次使用m download
命令,需要先安裝mod-tar
插件:
$ npm install mod-tar -g
-
如果您是一位女開發,請忽略下文直接聯系筆者,深圳優先。
模版結構
團隊的協作離不開一些基本的約定,Qing約定以下文件目錄結構:
. ├── css │ ├── main.css │ └── normalize.css ├── img ├── js │ ├── main.js │ └── vendor ├── tpl ├── .editorconfig ├── index.html └── Modfile.js
-
目錄
css
托管樣式文件 -
目錄
img
托管圖片文件 -
目錄
js
托管JavaScript文件 -
目錄
tpl
托管模版文件 -
.editorconfig
約定團隊基礎代碼風格 -
index.html
是入口HTML文件 -
Modfile.js
是Mod.js配置文件
模塊化編程指引
Qing推薦模塊化的開發過程,模塊化開發后無論在代碼可維護性與復用,還是團隊協作上都將變的更加直觀、輕松與高效。
CSS模塊化
通過原生CSS內置的@import機制管理CSS模塊,在構建過程中會自動合并壓縮(在下文的優化章節也有說明):
@import "normalize.css";@import "widget1.css";@import "widget2.css";@import "widget3.css";
JS模塊化
約定引入AMD規范來管理JS模塊,關于第一次接觸AMD的讀者,筆者推薦可以先Google了解后再進行下一步:
// main.jsdefine(["./app"], function(app){ app.init()})
// app.jsdefine(function(){ return { init: function(){} }})
HTML模塊化
HTML模塊指代HTML模版文件,通過requirejs-tmpl
插件將HTML分模塊管理,requirejs-tmpl
沒有默認打包在Qing模版中,可手動下載requirejs-tmpl插件至js目錄,或通過執行m download:tmpl
命令自動安裝插件:
<!-- tpl/headerTpl.html --><header><%= title %></header> <!-- HTMl模版可依賴其他HTML模塊 --><%@ ./navTpl.html %>
<!-- tpl/navTpl.html --><a href="<%= url %>">View On Github</a>
<!-- tpl/footerTpl.html --><footer><%= copyright %></header>
在HTML模版的引入是基于requirejs
的插件機制,所以在具體路徑前需加上tmpl!
前綴,表示其是HTML模版,例如:tmpl!../tpl/headerTpl.html
。 引用的模版已通過插件自動編譯,得到的函數如headerTpl
直接傳入需要綁定的數據即可:
// js/app.jsdefine(["tmpl!../tpl/headerTpl.html", "tmpl!../tpl/footerTpl.html"], function(headerTpl, footerTpl){ var html1 = headerTpl({title: "Hello Qing", url: "http://github.com/AlloyTeam/Qing"}) var html2 = footerTpl({copyright: "AlloyTeam"}) // balabala})
自動化工具的環境安裝
Mod.js是基于Node.js的工作流工具,安裝Node.js環境后使用NPM安裝Mod.js:
$ npm install modjs -g
一鍵構建
成功安裝Mod.js后, 進入Modfile所在的項目根目錄,只需執行m
命令,一切如此簡單,如假包換的一鍵構建:
$ m
執行完成后會在當前目錄下生成dist
目錄輸出構建后的結果。
性能優化
瀏覽器第一次請求服務器的過程至少需經過3RTTs:DNS域名解析1RTT;TCP連接建立1RTT;HTTP請求并且返回第一個比特的數據1RTT。 而這在移動基站網絡下請求則顯得異常緩慢,在我們的監測中,在2G網絡下僅DNS時間即可達到200ms,性能不容樂觀。 所以盡可能快的完成頁面加載在移動端顯得更加重要,而如何合理的減少頁面初始資源請求數是加快頁面加載最有效的方式:
合并JS模塊
Qing支持傳統的手動模塊加載管理與基于AMD的模塊加載管理方式,同時我們推薦使用Require.js作為開發過程中的模塊加載工具。
<!-- JS模塊模塊手動管理 --> <script src="js/fastclick.js"></script> <script src="js/spin.js"></script> <script src="js/main.js"></script>
傳統的手動添加模塊會自動合并,其按照合并連續引入資源的規則進行,最終輸出:
<script src="js/89ef9b6e.fastclick_main_3_520.js"></script>
<!-- data-main屬性值為執行入口JS文件地址 --> <script data-main="js/main" src="http://requirejs.org/docs/release/2.1.6/minified/require.js"></script>
通過模塊加載器方式,Qing會自動移除模塊加載器本身,其并不打包進最終輸出的文件:
<script src="js/89ef9b6e.main.js"></script>
Qing默認開啟的是移除define
生成模塊管理器無依賴代碼的stripDefine
優化模式。stripDefine
優化模式的配置在Modfile.js
的build任務中:
build: { src: "./index.html", stripDefine: true}
在stripDefine
優化模式下,基于AMD規范文件:
// base/clone.jsdefine(function(){ return function(obj){ return Object.create(obj) }})
// foo.jsdefine(['./base/clone'], function(clone){ return clone({foo:1})})
// bar.jsdefine(['./base/clone'], function(clone){ return clone({bar:2})})
// main.jsdefine('./foo', './bar'], function(foo, bar){ foo.bar = 2; bar.foo = 1;})
編譯后會在移除define的同時將模塊代碼轉換為變量聲明格式的代碼:
(function(window, undefined){ var base_clone = (function(){ return function(obj){ return Object.create(obj) } })(); var foo = (function(clone){ return clone({bar:2}) })(base_clone); var foo = (function(clone){ return clone({foo:1}) })(base_clone); var main = (function(foo, bar){ foo.bar = 2; bar.foo = 1; })(foo, bar);})(this)
合并CSS @imports
在頁面中引入了樣式文件css/main.css
:
<link rel="stylesheet" href="css/main.css">
而 css/main.css
中使用了CSS@import
機制來引入其他模塊的樣式文件:
@import "foo.css";@import "bar.css";@import "baz.css";
使用CSS原生@import
機制模塊化開發CSS是Qing推薦的方式,然不做優化直接發布到線上必然有性能問題,這是絕不允許的。
Qing在構建的時候會自動偵測所有引入的樣式文件是否使用了@import
,并進行合并優化。
合并連續引入資源
當頁面中引入了多個樣式文件或腳本文件:
<link rel="stylesheet" href="css/base.css"> <link rel="stylesheet" href="css/typo.css"> <link rel="stylesheet" href="css/main.css"> <script src="js/fastclick.js"></script> <script src="js/spin.js"></script> <script src="js/main.js"></script>
構建程序會將多個連續的靜態資源文件進行合并:
<link rel="stylesheet" href="css/89ef9b6e.base_main_3_630.css"> <script src="js/89ef9b6e.fastclick_main_3_520.js"></script>
data-rev配置
Qing會自動給所有優化后的靜態資源加上類似 89ef9b6e.
的指紋標示前綴來區分版本,此行為是默認打開, 可以通過data-no-rev
聲明來關閉,也可以data-rev
聲明開啟。
<html data-no-rev><link rel="stylesheet" href="css/main.css"> <script data-rev src="js/main.js"></script><img src="img/foo.png">
如上通過在HTML標簽中<html data-no-rev>
設置全局的策略,同時可在具體的標簽上覆蓋全局設置,如上構建后的結果:
<html><link rel="stylesheet" href="css/main.css"><script src="js/89ef9b6e.main.js"></script> <img src="img/foo.png">
data-stand-alone配置
有時要求某個基礎庫文件如jQuery能被不同頁面復用引入,而不是分別被打包在頁面級別的資源包內, 如此利用瀏覽器天然的緩存機制使無需重新請求相同的資源內容, Qing在默認構建約定的基礎上同時提供了基于DOM的data-stand-alone
配置。
<script data-stand-alone src="vendor/jquery-2.0.3.js"></script> <script src="js/foo.js"></script><script src="js/bar.js"></script> <script src="js/baz.js"></script>
構建結果:
<script data-stand-alone src="vendor/92cf6237.jquery-2.0.3.js"></script> <script src="js/92cf6237.foo_baz_3_168.js"></script>
data-group配置
如何重復利用瀏覽器的并發請求數但同時考慮不至于有過多請求數上的負載,在不同場景下優化策略會有不同: 當需兼容IE老版本的情況下,初始并發請求數不推薦超過2個,但同時我們推薦單個資源包的大小Gzip前不超過200k, 所以通常如何來控制打包粒度是需要監控數據來支撐的。Qing在構建中提供了data-group
分組參數來輔助打包粒度的控制:
<script data-group=1 src="js/foo.js"></script> <script data-group=1 src="js/bar.js"></script> <script data-group=1 src="js/baz.js"></script> <script data-group=2 src="js/qux.js"></script> <script data-group=2 src="js/quux.js"></script> <script data-group=2 src="js/corge.js"></script>
構建結果:
<script data-group=1 src="js/92cf6237.foo_baz_3_168.js"></script> <script data-group=2 src="js/090430cf.qux_corge_3_171.js"></script>
data-url-prepend配置
資源CDN化是基本的優化策略,
<html data-url-prepend="http://cdn1.qq.com/"> <script data-group=1 src="js/foo.js"></script> <script data-group=1 src="js/bar.js"></script> <script data-group=1 src="js/baz.js"></script> <script data-group=2 data-url-prepend="http://cdn2.qq.com/" src="js/qux.js"></script> <script data-group=2 src="js/quux.js"></script> <script data-group=2 src="js/corge.js"></script>
構建結果:
<html> <script data-group=1 src="http://cdn1.qq.com/js/92cf6237.foo_baz_3_168.js"></script> <script data-group=2 src="http://cdn2.qq.com/js/090430cf.qux_corge_3_171.js"></script>
內嵌靜態資源
所謂減少請求數最優的目標就是沒有請求,Qing提供了基于QueryString的embed
配置使支持在構建時將靜態資源內嵌于HTML中, 如此便可優化至最理想的情況:只需下載必不可少的HTML資源文件。
內嵌樣式
<link rel="stylesheet" href="css/base.css?embed"> <link rel="stylesheet" href="css/typo.css"> <link rel="stylesheet" href="css/main.css">
構建結果:
<style>css/base.css...css/typo.css...css/main.css...</style>
內嵌腳本
<script src="js/fastclick.js?embed"></script> <script src="js/spin.js"></script> <script src="js/main.js"></script>
構建結果:
<script>js/fastclick.js...js/spin.js...js/main.js...</script>
內嵌圖片
內嵌CSS里
#foo { background: url('../img/icon.png?embed') no-repeat; height: 24px; width: 24px}
構建結果:
#foo { background: url(data:image/png;base64,iVBORw0...) no-repeat; height: 24px; width: 24px}
內嵌HTML里
<img src="./img/icon.png?embed">
構建結果:
<img src="data:image/png;base64,iVBORw0...">
基礎庫
Qing總是想法設法的讓開發過程更自動更流暢,在Qing模版的Modfile.js
中提供了以下第三方庫的下載配置:
-
FastClick
-
Spin.js
-
Zepto
-
jQuery 1.x
-
jQuery 2.x
-
require.js 2.1.9
-
requirejs-tmpl
截取Modfile.js
中關于第三方庫的配置,src表示源地址,dest表示下載目錄, 除了tmpl插件下載至js/
目錄其他所有第三方庫都默認下載至js/vendor/
目錄:
{ options: { dest: "js/vendor/" }, fastclick: { src: "https://raw.github.com/ftlabs/fastclick/master/lib/fastclick.js" }, spin: { src: "https://raw.github.com/fgnass/spin.js/gh-pages/dist/spin.js" }, zepto: { src: "http://zeptojs.com/zepto.js" }, jquery1: { src: "http://code.jquery.com/jquery-1.10.2.js" }, jquery2: { src: "http://code.jquery.com/jquery-2.0.3.js" }, requirejs: { src: "http://requirejs.org/docs/release/2.1.9/comments/require.js" }, tmpl: { dest: 'js/', src: "https://raw.github.com/modulejs/requirejs-tmpl/master/tmpl.js" }}
下載全部庫至本地方式非常簡單,只需在根目錄下執行:
$ m vendor
如只需下載Zepto:
$ m download:zepto