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

weixin_34205076發表於2017-11-16

說到 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 之後模組不能以檔案為單位,所以需要重用的模組都應該是命名模組。考慮到具體頁面自己的模組不需要重用,所以這種情況下可以定義為匿名模組。所以模組定義函式要像這樣:

    1
    2
    3
    4
    5
    6
    7
    funciton define(name, factory) {
        if (isFunction(name)) {
            factory = name
            name = undefined
        }
        // ......
    }

    模組名稱唯一性由人來控制,但是應提供檢查機制,所以如果出現重複定義的情況,丟擲異常。由是在一個專案中,命名衝突這種情況應該不是主要矛盾。如果不幸命名衝突成為了主要矛盾,基本上也可以通過定義名稱空間來解決。最簡單的名稱空間就是在模組名中加入名稱空間部分,比如 "app.core.codec.hexcode"


  2. 按需載入,使用 require 函式

    1
    2
    3
    function require(moduleName) {
        // ......
    }


  3. 執行模組的入口。雖然可以用 require 作為入口,但是 require 需要一個模組名稱作為引數,不能用於匿名模組作為入口的情況。假想如下應用場景:

    1
    2
    3
    define(function() {
        // ......
    }).use()

    要實現這種應用場景,就需要 define 返回一個物件,該物件擁有 use 方法,可以通過 use 方法一次性呼叫當前模組的 factory 函式。比較簡單直接的方式就是在內部定義一個 Module 類來裝載模組配置,在 define 的時候生成 Module 物件,並返回出來。

    1
    2
    3
    4
    5
    6
    function define(name, factory) {
        // ......
        var module = new Module(factory)
        // ......
        return module
    }


  4. 內部模組管理。通過一個 map<name, module> 來管理所有模組定義,這在實現上就是一個普通的 JavaScript 物件。匿名模組因為是立即使用,所以不需要進行管理。模組管理的核心其實是 Module 類,需要通過它完成建立模組、快取匯出物件和提供匯出物件等。而且除了 use 方法需要暴露出來之後,其它方法都應該隱藏起來。

    經過參考、推敲和實驗,得出瞭如下的一個程式碼框架

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 這是所有命名模組儲存的地方
    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 的基礎之上發展而來的。如果有興趣的話,請關注一下這個專案。



本文轉自邊城__ 51CTO部落格,原文連結:http://blog.51cto.com/jamesfancy/1598533,如需轉載請自行聯絡原作者

相關文章