2014-01-04

More in yepnope 1.5.4

前一篇筆記「yepnope.js 讓瀏覽器只載入需要的檔案」只提到條件式載入,這邊再來看看 yepnope 還有哪些功能。

官方定義

yepnope is an asynchronous conditional resource loader that's super-fast, and allows you to load only the scripts that your users need.
歸納的重點如下:
  • 非同步的方式
  • 依據指定的條件
  • 超快的
  • 載入所需的檔案

非同步的方式下載?

最重要的一點就是,依據該 resource 的程式必須放在 callback 裡,不能緊接在 yepnope 之後。
yepnope([ 'http://code.jquery.com/jquery-1.10.2.min.js', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ]);
jQuery('div').dialog({});
一定掛掉!
yepnope([ {
  load : [ 'http://code.jquery.com/jquery-1.10.2.min.js', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ],
  complete : function() {
    $(function() {
      $('div').dialog({});
    });
  }
} ]);
這樣才行。

還有兩個值得一提的特色:
  • 下載與執行分家
  • 同時下載,分開執行
使用 yepnope 下載多個檔案時,yepnope 會先同時下載每個檔案,待完全下載完成後,才逐個執行

還有很多 Script loader 可以用?

yepnope.js 的優點有:
  • 檔案超小,目前版本(1.5.4)的壓縮版只有 4 KB。
  • yepnope 不叫 script loader,是叫做 resource loader,因為除了可以下載 js 檔,還可以下載 css 檔。
  • 經過 QUnit 與 TestSwarm 完整的測試。 
  • 將下載與執行拆開來,可以明確指定執行的時機與順序。
  • 簡單好用的 API,可以利用 array 將 resource 群組。
  • 模組化,可加入自訂的功能(Prefixes 與 filters)。
  • 也許不是最快的 Script loader,但可以只載入想要的 resource,所以總的來說是較快的。
  • 與偵測瀏覽器 HTML5 與 CSS3 功能的 Modernizr 整合
  • 預設依照表列的順序執行下載,但可以改寫  yepnope.injectJs 與 yepnope.injectCss 自訂需求。
  • 神奇的 resource fallback 功能,稍後說明。

yepnope 1.5 的新功能

  • 同樣的 url 不會執行兩次,但其 callback 會依順序執行
  • 新增 yepnope.injectJs 與 yepnope.injectCss 兩個 API,方便直接使用

不用 Script loader 的作法

將所有用到的 script 組成一個 js 檔放在網頁最後面,這樣做有些缺點,首先,一定會下載到一些這一頁沒用到的 js,再來就是下載一個大檔的速度一定比下載多個小檔來的慢,因為多個小檔是同時下載的,最後是沒有 yepnope 提供的特殊功能,「功能測試(Feature Test)」,可以依據瀏覽器所提供的功能(或沒有提供的功能)下載所需的 js 檔,這在「yepnope.js 讓瀏覽器只載入需要的檔案」已經說明過了。

少少的 API

只有 5 個 functions,yepnope 是 yepnope.js 的核心 function。

yepnope

API
yepnope(resources /* string | object | array */)
可以傳入的參數有三種:
  • string:表示要載入檔案的 url 字串。
  • test object:包含 7 個屬性的 object。
  • array:array of string、array of test object 或甚至 array of array,可以混搭,也就是一個 array 裡可以同時有 string、test object 或另一個 array。
範例
// string
yepnope('http://code.jquery.com/jquery-1.10.2.min.js');

// array of string
yepnope([ 'http://code.jquery.com/jquery-1.10.2.min.js', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ]);

// test object
yepnope({
  load : [ 'http://code.jquery.com/jquery-1.10.2.min.js', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ]
});

// array of test object
yepnope([ {
  load : 'jquery-1.10.2.min.js'
}, {
  load : 'jquery-ui-1.10.3.min.js'
}, {
  load : 'jquery-ui-1.10.3.min.css '
} ]);

test object

{
  test : expression,
  yep  : resources,
  nope : resources,
  both : resources,
  load : resources,
  callback : callback function,
  complete : callback function
}
所有屬性都不是必填。

由於 yepnope.js 是用來下載 js 或 css 檔,因此只要副檔名不是 css 就會被當成 js 檔,若真的遇到副檔名不是 css 的 css 檔案時,可以另外將 yepnope.css.js plugin 加入即可(why?TODO)。
  • test:運算式,用來判斷要載入 yep(true)或 nope(false)指定的檔案,test / yep / nope / both 應搭配使用,若無設定 test,預設為 false,雖然不會這麼用,但可以沒有 test 卻有 nope,等於是 load 的效果。
  • yep:test  為 true 時想要載入的檔案,可為 string、array of string 或 object of string(自訂 key 時使用),test / yep / nope / both 應搭配使用。
  • nope:test  為 false 時想要載入的檔案,可為 string、array of string 或 object of string(自訂 key 時使用),test / yep / nope / both 應搭配使用。
  • both:不管 test 結果,每次都要載入的檔案,可為 string、array of string 或 object of string(自訂 key 時使用),test / yep / nope / both 應搭配使用。
  • load:要載入的檔案,可為 string、array of string 或 object of string(自訂 key 時使用),基本上 load 不會與 test / yep / nope / both 同時使用,是可以一起用但邏輯不對,另外 load 與 both 同時使用時,不會執行 both。
  • callback:每個 resource 下載完成後執行的 callback function 或 object of function(自訂 key 時使用),同時下載多個 resource 時,callback 就會執行多次,傳入三個參數依序為 url, result 與 key。
    • url 為指定要下載的檔案,可能是 yep、nope、both 或 load 設定的,可供識別是哪個 resource 的 callback 呼叫。
    • result 為 test 的結果(true / false),若無 test,預設為 false。
    • key 為檔案的 key,如果有設定的話,如果沒有設定 key,若是有設定的話,如果沒有設定 key,用 array,那就是 array index,如果不是用 array,只是個 string 或 test object 的話,那就是 0。
  • complete:所有 resource 下載完成後執行的 callback function,不管下載幾個 resource,只會執行一次。
// 如果不指定 test,就單純作為 resource loader
yepnope({
  load : [ 'http://code.jquery.com/jquery-1.10.2.min.js', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ]
});

// yep 與 nope 不一定都要有
yepnope({
  test : window.jQuery,
  nope: 'http://code.jquery.com/jquery-1.10.2.min.js'
});

// 同時載入多個 resource 的第一種用法 - 一個 test object
// callback 執行三次,url 為 load 參數的設定值,result 皆為 false,key 為 0, 1, 2
// complete 執行一次,在 callback 執行三次後執行
// 這應該是三種用法中比較常見的方式
yepnope({
  load : [ 'http://code.jquery.com/jquery-1.10.2.min.js', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ],
  callback : function(url, result, key) {
    alert(url + ', ' + result + ', ' + key + ' loaded');
  },
  complete : function() {
    alert('complete');
  }
});

// 同時載入多個 resource 的第二種用法 - 多個 test object
// callback 執行三次,complete 執行三次
// 依序為第一個 resource 的 callback、第一個 resource 的 complete
// 然後是第二個 resource 的 callback、第二個 resource 的 complete
// 最後是第三個 resource 的 callback、第三個 resource 的 complete
yepnope([ {
 load : 'jquery-1.10.2.min.js',
 callback : function() {
   alert('jquery-1.10.2.min.js loaded');
 },
 complete : function() {
   alert('jquery-1.10.2.min.js complete');
 }
}, {
 load : 'jquery-ui-1.10.3.min.js',
 callback : function() {
   alert('jquery-ui-1.10.3.min.js loaded');
 },
 complete : function() {
   alert('jquery-ui-1.10.3.min.js complete');
 }
}, {
 load : 'jquery-ui-1.10.3.min.css',
 callback : function() {
   alert('jquery-ui-1.10.3.min.css loaded');
 },
 complete : function() {
   alert('jquery-ui-1.10.3.min.css complete');
 }
} ]);

// 同時載入多個 resource 的第三種用法 - 多個 yepnope 呼叫
// callback 與 complete 呼叫同第二種用法
yepnope({
  load : 'jquery-1.10.2.min.js',
  callback : function() {
    alert('jquery-1.10.2.min.js loaded');
  },
  complete : function() {
    alert('jquery-1.10.2.min.js complete');
  }
});
yepnope({
  load : 'jquery-ui-1.10.3.min.js',
  callback : function() {
    alert('jquery-ui-1.10.3.min.js loaded');
  },
  complete : function() {
    alert('jquery-ui-1.10.3.min.js complete');
  }
});
yepnope({
  load : 'jquery-ui-1.10.3.min.css',
  callback : function() {
    alert('jquery-ui-1.10.3.min.css loaded');
  },
  complete : function() {
    alert('jquery-ui-1.10.3.min.css complete');
  }
});

// 可在 yep、nope、both 與 load 將 array 換成 object,並加上 key(object of string)
// 如此就可以在 callback 裡使用 key 判斷是哪個 resource 的 callback
// 以取代使用一大串的 url 做判斷
yepnope({
  load : {
    'jquery' : 'http://code.jquery.com/jquery-1.10.2.min.js',
    'jquery-ui' : 'jquery-ui-1.10.3.min.js',
    'ui-css' : 'jquery-ui-1.10.3.min.css'
  },
  callback : function(url, result, key) {
    if (key == 'jquery') {
      alert('jquery loaded');
    }
  }
});

// 使用 key 的進化版,可以將單一的 callback function 換成 object of function
yepnope({
  load : {
    'jquery' : 'http://code.jquery.com/jquery-1.10.2.min.js',
    'jquery-ui' : 'jquery-ui-1.10.3.min.js',
    'ui-css' : 'jquery-ui-1.10.3.min.css'
  },
  callback : {
    'jquery' : function(url, result, key) {
      alert('jquery loaded');
    },
    'jquery-ui' : function(url, result, key) {
      alert('jquery-ui loaded');
    }
  }
});

下載與執行順序

所有檔案都是同時下載,但依照設定的順序執行。
  • 第一個 yepnope 呼叫的檔案先於第二個 yepnope 呼叫的檔案執行。
  • yep 與 nope 先於 load 與 both 的檔案執行。
  • Array of string, test object, or array 則按照 array 中的順序執行。

Recursive yepnope

遞迴?是的,可以在 callback 裡呼叫另一個 yepnope,目的是稍後提到的 Resource Fallback。

但有一個很大的問題,下載與執行順序呢?

因為是在 callback 裡觸發新的下載,所以是先後下載,不是原本的同時下載,這有傷效能,但對於真的需要 Resource Fallback 時,這是有幫助的。

神奇的地方在於執行的順序,前一段提到執行順序是按照檔案設定的順序,但對於 Recursive yepnope 呢?按照 Resource Fallback 的邏輯來看,Recursive yepnope 的檔案必須與呼叫它的外部 yepnope 檔案相同順序,的確是這樣沒錯。

例如第一個 yepnope 下載遠端的 jQuery,第二個 yepnope 下載遠端的 jQuery UI,因為兩個檔案有相依性,所以在第一個 yepnope callback 針對下載失敗的時候,另外下載本地端的 jQuery,yepnope 對下載檔案的執行順序,依舊先是 jQuery(本地端),然後才是遠端的 jQuery UI,範例請見稍後的 Resource Fallback。

yepnope.injectJs

直接載入 Javascript 檔。

API
yepnope.injectJs( scriptSource [, callback ] [, elemAttributes ] [, timeout ]);
範例
<!DOCTYPE html>
<html>
<head>
<meta charset="BIG5">
<title>RWD</title>
<script src="yepnope.1.5.4-min.js"></script>
<script>
  alert(window.jQuery);
  yepnope.injectJs("jquery-1.10.2.min.js", function() {
    alert(window.jQuery);
  }, {
    charset : "utf-8"
  }, 5000);
</script>
</head>
<body>
</body>
</html> 

yepnope.injectCss

直接載入 CSS 檔。

API
yepnope.injectCss( stylesheetSource [, callback ] [, elemAttributes ] [, timeout ]);
範例
<!DOCTYPE html>
<html>
<head>
<meta charset="BIG5">
<title>RWD</title>
<script src="yepnope.1.5.4-min.js"></script>
<script>
  yepnope.injectCss("print.css", function() {
    // print.css loaded
  }, {
    media : "print"
  }, 5000);
</script>
</head>
<body>
</body>
</html>

Resource Fallback

如果要載入的 resource 有相依性時,例如 jQuery UI 需要 jQuery,當 jQuery 載入失敗而 jQuery UI 載入成功,應該要如何處理?yepnope 提供 fallback。
yepnope([ {
  load : ['http://code.jquery.com/jquery-1.10.2.min.jsp', 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css'],
  complete : function() {
    jQuery(function() {
      jQuery('div').dialog({});
    });
  }
} ]);
故意寫錯第一個網址,會得到以下的結果,炸掉了。


可以換個較安全的寫法。
yepnope([ {
  load : 'http://code.jquery.com/jquery-1.10.2.min.jsp',
  complete : function() {
    if (!window.jQuery) {
      yepnope('jquery-1.10.2.min.js');
    }
  }
}, {
  load : [ 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ],
  complete : function() {
    jQuery(function() {
      jQuery('div').dialog({});
    });
  }
} ]);
拆成兩個 test object,在第一個 test object 的 complete callback 檢查是否載入成功,若為否,則可以立即載入本機版本,yepnope 會協調順序,不會讓第二個 test object 的 complete callback 在第一個前執行,因此可以得到正確的結果。

這也是 yepnope 特點之一,同時下載但依照指定的順序執行。


yepnope.errorTimeout

前一個故意寫錯網址的範例,得等好一會才會執行 Resource Fallback,這一段時間是 Timeout 設定(yepnope 預設為 10 秒),可以使用 yepnope.errorTimeout 來減少等待時間。
yepnope.errorTimeout = 1000;
yepnope([ {
  load : 'http://code.jquery.com/jquery-1.10.2.min.jsp',
  complete : function() {
    if (!window.jQuery) {
      yepnope('jquery-1.10.2.min.js');
    }
  }
}, {
  load : [ 'jquery-ui-1.10.3.min.js', 'jquery-ui-1.10.3.min.css' ],
  complete : function() {
    jQuery(function() {
      jQuery('div').dialog({});
    });
  }
} ]);

重複下載?

在 Resource Fallback 的圖裡,可以看到每個檔案都出現兩次,不會真的下載兩次吧?不是的,只要看看下載時間就知道了。

因為 yepnope 的下載與執行分家的關係,所以同樣的檔案會出現兩次,第一次是下載,第二次是執行。

Prefix 與 Filter

這兩個功能已經有點走太遠了,這邊只看內建的 timeout prefix,前面提過可以透過 yepnope.errorTimeout 來設定 request timeout 時間,但這是全域的設定,也就是接下來每個 yepnope 都會受到影響,timeout prefix 的好處有二,第一個是設定簡單,第二個是針對單一 yepnope 呼叫設定。
yepnope({
  load : [ 'timeout=1000!jquery-1.10.2.min.jsp' ],
  callback : function(url, result, key) {
    alert(window.jQuery);
  }
});
其他沒有內建的 css prefix、preload prefix 與 ie prefix 就跳過了,原因是 css 與 preload prefix 不知道要用在哪裡,而 ie prefix 則試不出來官網說的效果。

限制

不能在 yepnope 裡使用 document.write,也就是 Google Map 與 Ads 都不能使用 yepnope 來載入。

IE9 以前的版本不能保證 callback 會在下載的檔案執行後立即執行。

不可以在  yepnope callback 裡操作 DOM,因為在 yepnope 下載檔案並執行後,並不保證 DOM 已經完成了,所以建議在 yepnope callback 裡另外使用 document ready callback 來操作 DOM。
yepnope({
  load : [ 'jquery-1.10.2.min.jsp' ],
  callback : function(url, result, key) {
    $(function(){
      $('div').html(...);
    });
  }
});
不建議使用 yepnope 來下載過多的 js,事實上下載過多的 js 是不建議的方式,不論是否使用 yepnope ,建議合併類似的檔案,但不要全併成一個檔案。

舊 IE 限制同一個 domain 同一時間最多只能下載兩個檔案,新一點的 IE 則是 6 個檔案,只能併檔或放到子 domain。
---
---
---

沒有留言:

張貼留言