天天看點

ASP.NET MVC4 捆綁(Bundle)技術下的 JavaScript

說到 Web 應用中 JavaScript 的子產品化,很容易想到 RequireJS、SeaJS 和 ECMAScript 6。ES6 要全面應用還得有段時間,RequireJS 和 SeaJS 的子產品化在實際應用中又有兩個分支:一是通過按需加載的方式加載并建立子產品,二是通過工具打包成單一檔案,一次性加載,按需建立子產品。ASP.NET MVC4 的捆綁(Bundle)技術類似後者。

MVC4 Bundle 主要用于優化 JavaScript 和 CSS 資源的加載。關于這個技術的介紹,可以參考《ASP.NET Mvc4 Bunlde 捆綁壓縮技術》,或者《CSS程式設計:捆綁和縮小》。其特點很鮮明,主要有兩點:

  1. 在開發環境,加載原檔案,便于定位和 Debug;
  2. 在生産環境,按配置将所有資源分類打包壓縮,優化浏覽器對資源加載。

也正是由于它的這兩個特點,如果要使用 Bundle 技術,就很難使用現有的 JavaScript 子產品化工具來進行開發。翻了下百度和 Google,沒找到合适的解決方案,于是決定自己寫個簡單的子產品加載器,主要實作如下目标:

  1. 子產品化開發
  2. 大部分 JavaScript 檔案由 MVC4 一次性加載,但子產品按需建立
  3. 部分頁面的腳本,可以按頁面需要單獨加載,但同樣是子產品化的

分析目标,歸整一下,大概有如下要點需要實作

  1. 由于 Bundle 之後子產品不能以檔案為機關,是以需要重用的子產品都應該是命名子產品。考慮到具體頁面自己的子產品不需要重用,是以這種情況下可以定義為匿名子產品。是以子產品定義函數要像這樣:
    funciton define(name, factory) {
        if (isFunction(name)) {
            factory = name
            name = undefined
        }
        // ......
    }      
    子產品名稱唯一性由人來控制,但是應提供檢查機制,是以如果出現重複定義的情況,抛出異常。由是在一個項目中,命名沖突這種情況應該不是主要沖突。如果不幸命名沖突成為了主要沖突,基本上也可以通過定義命名空間來解決。最簡單的命名空間就是在子產品名中加入命名空間部分,比如

    "app.core.codec.hexcode"

  2. 按需加載,使用

    require

    函數
    function require(moduleName) {
        // ......
    }      
  3. 執行子產品的入口。雖然可以用

    require

    作為入口,但是

    require

    需要一個子產品名稱作為參數,不能用于匿名子產品作為入口的情況。假想如下應用場景:
    define(function() {
        // ......
    }).use()      
    要實作這種應用場景,就需要

    define

    傳回一個對象,該對象擁有

    use

    方法,可以通過

    use

    方法一次性調用目前子產品的 factory 函數。比較簡單直接的方式就是在内部定義一個

    Module

    類來裝載子產品配置,在

    define

    的時候生成

    Module

    對象,并傳回出來。
    function define(name, factory) {
        // ......
        var module = new Module(factory)
        // ......
        return module
    }      
  4. 内部子產品管理。通過一個

    map<name, module>

    來管理所有子產品定義,這在實作上就是一個普通的 JavaScript 對象。匿名子產品因為是立即使用,是以不需要進行管理。子產品管理的核心其實是

    Module

    類,需要通過它完成建立子產品、緩存導出對象和提供導出對象等。而且除了

    use

    方法需要暴露出來之後,其它方法都應該隐藏起來。

    經過參考、推敲和實驗,得出了如下的一個代碼架構

    // 這是所有命名子產品儲存的地方
    var modules = {}
    
    function Module(name, factory) {
        // 建立子產品對象,儲存 factory 函數
    }
    
    Module.prototype.use = function() {
        // 執行 factory 函數
        // 處理 exports 和 isExported 等狀态
        return exports
    }
    
    function define(name, factory) {
        // 定義并儲存子產品
        modules[name] = new Module(name, factory)
    }
    
    function require(name) {
        // 按名稱找到子產品,并執行之
        return modules[name].use()
    }      

在最終實作的時候,還需要處理容錯,以及若幹細節問題。最終代碼命名為

js-modular.js

,在附件中可以下載下傳。在使用的時候隻需要注意一點,頁面上加載腳本的時候,記得把

js-modular.js

放在所有子產品定義腳本之前即可。

目前已經建立了開源項目 jNs,基于命名空間的子產品管理工具,是在 js-modular.js 的基礎之上發展而來的。如果有興趣的話,請關注一下這個項目。

js-modular.js 及 Demo(VS2013 + MVC4 + NuGet)下載下傳

繼續閱讀