基於Grunt構建一個JavaScript庫

顏海鏡發表於2014-07-14

現在公認的JavaScript典型專案需要執行單元測試,合併壓縮。有些還會使用程式碼生成器,程式碼樣式檢查或其他構建工具。

Grunt.js是一個開源工具,可以幫助你完成上面的所有步驟。它非常容易擴充套件,並使用JavaScript書寫,所以任何為JavaScript庫或專案工作的人都可以按自己的需要擴充套件它。

本文解釋如何使用Grunt.js構建JavaScript庫。Grunt.js依賴Node.js和npm,所以第一節解釋其是什麼,如何安裝和使用。如果你對npm有了解,那你可以跳過這一節。第四和第五節講述如何配置Grunt和一系列典型Grunt任務。

本文討論的程式碼例子可以在GitHub上訪問。

工具概述(Tool Chain Overview)

開始之前,我們需要三個工具:

Node.js是一個流行的伺服器端JavaScript環境。它被用來編寫和執行JavaScript服務和JavaScript命令列工具。如果你想進一步連結Node.js,你可以檢視Stack Overflow上相關的資料。

Npm是Node.js的包管理工具。它能從中心倉庫下載依賴,解決大部分依賴衝突問題。Npm倉庫僅僅儲存Node.js伺服器段和命令列專案。它不包含用於web和移動app相關的庫。我們用它來下載Grunt.js。

Grunt.js是一個任務執行工具,我們用起構建我們的專案。它在Node.js之上執行並且通過Npm安裝。

安裝Node.js和Npm(Node.js and Npm Installation)

你可以直接從下載頁面或用其它包管理工具安裝node.js。安裝成功後在命令列輸入 node -v Node.js將輸出它的版本號。

大部分安裝器和包管理工具將同時安裝Npm。在命令列輸入 npm -v 測試是否安裝成功。如果成功將輸出它的版本號。不同的系統可能安裝方式略有不同。

Linux

下載和使用安裝指令碼

Windows

windows安裝器包含npm並會新增path變數。僅在你下載Node.exe或從原始碼編譯Node是才需要獨立安裝Npm。從這裡下載Npm的最新版zip壓縮包。解壓後賦值到Node.exe的安裝目錄。如果你願意,你也可以放到任何位置,將其加入path變數即可。

OSX

安裝包中內建Npm。

Npm基礎(Npm Basics)

瞭解Npm基礎操作對於使用和安裝Grunt.js都有幫助。這節僅包含接觸知識。更多細節可以檢視npm文件

本節將解釋下面的東西:

  • 什麼是npm;
  • npm外掛本地安裝和全域性安裝的區別;
  • package.json檔案和其規範;
  • npm安裝命令。

概要(Overview)

Npm是一個包管理工具,可以從中心倉庫下載和安裝JavaScript依賴。安裝包能被用在Node.js專案或命令列工具。

專案通常在package.json檔案內部列出其依賴和安裝外掛。此外npm庫也可以從命令列安裝。

全域性安裝vs本地安裝(Global vs Local Installation)

每個包可以安裝在全域性或本地環境。實際的區別是儲存位置和訪問方式。

全域性安裝包被直接儲存在Node.js安裝路徑。他們之所以被稱為全域性,是因為他們可以在任何地方直接訪問。

本地安裝將下載包安裝在當前工作路徑。本地安裝包只能從其所在目錄訪問。

本地安裝包被儲存進node_mudules子目錄。無論你使用什麼版本控制系統,你可以將其新增僅.ignorefile 檔案。

Package.json

package.json檔案包含npm專案描述。它總是位於專案的根目錄,並且包含專案名稱,版本,協議和其他類似後設資料。最重要的是,它包含兩個專案依賴列表。

 第一個列表包含執行所需要的依賴。任何希望使用此專案的人必須安裝它們。第二個列表包含開發時需要依賴項。包括測試工具,構建工具和程式碼樣式檢測工具。

建立package.json最簡單的方法是通過 npm install 命令。這條命令會以互動式提問一系列問題,並根據回答在當前工作目錄生成基本的package.json檔案。只有名字(name)和版本(version)屬性是必須的。如果你不打算將你的庫釋出到Npm,你能忽略其餘的部分。

下面的連結包含對package.json的詳細描述:

安裝命令(The Install Command)

Npm寶可以通過npm install 命令安裝。預設安裝到本地。全域性安裝需要指定 -g開關。

不帶引數的 npm install 將在當前目錄或上層目錄查詢 package.json 檔案。如果發現,將會在當前目錄安裝所有列出的依賴項。

可以通過 npm install <pkg_name@version> 命令安裝具體的npm包。這條命令將從中心倉庫找到指定版本的包,並將其安裝到當前目錄。

版本號是可選的。如果省略將下載最新穩定版。

最後,通過 --sace-dev開關不僅可以安裝包,還會將其新增到 package.json 的開發依賴中。

為專案新增Grunt.js(Adding Grunt.js to the Project)

我們將首先將Grunt.js新增進我們的JavaScript專案。為此我們需要安裝兩個Grunt.js模組:

  • grunt-cli - 命令列介面 (CLI);
  • grunt - 任務執行器.

提醒:最新的Grunt.js(4.0)不再相容以前的版本。一些老的教程和文件不再適合新版Grunt.js了。

概論(Overview)

所有實際的工作是由任務執行器來做。命令列介面僅解析引數和將其傳遞個任務執行器。如果任務執行器沒有安裝將不會做任何事情。

命令列介面應該被安裝在全域性環境,然而任務執行器在本地環境。全域性命令列介面保證Grunt命令可以在所有路徑訪問。任務執行器必須是本地的,因為不同的專案可能需要不同的Grunt版本。

安裝(Installation)

安裝全域性Grunt命令列介面:

npm install -g grunt-cli

切換到專案根目錄,通過npm init讓Npm幫你生成package.json檔案。它會問你些問題然後根據你的回答生成合法的package.json檔案。只有名字和版本是必須的;你可以忽略其他選項。

將Grunt.js最新版新增到本地環境,同時新增到package.json檔案的開發依賴裡。

npm install grunt --save-dev

Package.json

通過前面的命令建立的package.json檔案應該類似相面這樣:

{
  "name": "gruntdemo",
  "version": "0.0.0",
  "description": "Demo project using grunt.js.",
  "main": "src/gruntdemo.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": "",
  "author": "Meri",
  "license": "BSD",
  "devDependencies": {
    "grunt": "~0.4.1"
  }
}

配置Grunt.js(Configure Grunt.js)

Grunt.js執行任務並且通過任務完成工作。然而,Grunt.js安裝成功後還沒有任務可以使用。任務必須從外掛載入,而外掛通常需要Npm安裝。

我們使用五個外掛:

  • grunt-contrib-concat - 拼接檔案,
  • grunt-contrib-uglify - 拼接並壓縮檔案(js),
  • grunt-contrib-copy - 複製檔案,
  • grunt-contrib-qunit - 執行單元測試,
  • grunt-contrib-jshint - 檢查bug和JavaScript程式碼風格.

本節將解釋如何配置這些外掛。我們從最簡單的配置開始,然後一步步解釋如何配置任務。餘下的子章節將詳細講解如何配置每個外掛。

基礎-不做任何配置(Basic Do Nothing Configuration)

Grunt將配置資訊寫到Gruntfile.js或Gruntfile.coffee檔案裡。由於我們建立的JavaScript專案,我們將使用JavaScript版本。最簡單的Gruntfile.js看起來像下面這樣:

//包裝函式 有一個引數
module.exports = function(grunt) {

  // 預設任務。在本例子中沒有任何操作。
  grunt.registerTask('default', []);
};

配置資訊被儲存在module.exports函式內部。它包含grunt物件作為其引數,並且通過呼叫該函式完成配置。

配置函式必須建立至少一個任務別名,並且配置他們。例如,上面的程式碼片段建立了一個“default”任務別名並且指定為空的任務列表。換句話說,預設的任務別名可以工作,但不會做任何事情。

用 grunt <taskAlias> 命令執行指定的 taskAlias任務。taskAlias的引數是可選的,如果省略,Grunt將使用“default”任務。

儲存Gruntfile.js檔案,在命令列執行Grunt:

grunt

你應該看到如下輸出:

Done, without errors.

如果配置任務返回錯誤或警告Grunt將發出蜂鳴聲(在命令列下輸入錯誤的聲音,譯者未發現這點)。如果你不想聽到蜂鳴聲,你可以使用 -no-color 引數:

grunt -no-color

Grunt Npm 任務(Grunt Npm Tasks)

從外掛新增任務是通過用步驟,對所有外掛都是相同的。本節將概要講述需要的過程,實際的例子將在下面的章節講解。

安裝外掛(Install the Plugin)

首先,我們需要將外掛新增進package.json檔案的開發依賴裡面,並且使用Npm進行安裝:

npm install <plugin name> --save-dev

配置任務(Configure Tasks)

任務配置必須被儲存在一個物件內部,有各自的任務名,並且被傳遞給 grunt.initConfig方法:

module.exports = function(grunt) {

  grunt.initConfig({
    firstTask : { /* ... 配置第一個任務 ... */ },
    secondTask : { /* ... 配置第二個任務 ... */ },
    // ... 其他任務 ...
    lastTask : { /* ... 最後一個任務 ... */ }
  });

  // ... the rest ... 
};

全面的任務配置資訊解釋看這裡Grunt.js文件。本節僅描述最通用,簡單的例子。假設任務接受一個檔案列表,並處理他們,然後生出輸出檔案。

一個簡單的任務配置例子:

firstTask: {
  options: {
    someOption: value //取決於外掛
  },
  target: {
    src: ['src/file1.js', 'src/file2.js'], //輸入檔案
    dest: 'dist/output.js' // 輸出檔案
  }
}

例子中的任務配置有兩個屬性。一個是任務選項,名稱必須是”options“。Grunt.js不會對options的屬性執行任何操作,其行為有外掛決定。

其他項可以有任何名字,並且要包含任務目標。最常見的任務是操作和生成檔案,所以他們的target有兩個屬性,”src“ 和 ”dest“。src包含輸入的檔案列表,dest包含輸出的檔名字。

如果你配置多個任務,Grunt將依次執行。下面的任務將執行兩次,一次操作src及其子目錄的所有js檔案,另一次操作test及其子目錄下的所有js檔案:

multipleTargetsTask: {
  target1: { src: ['src/**/*.js'] },
  target2: { src: ['test/**/*.js']] }
} 

載入和註冊任務(Load and Register Tasks)

最後,將外掛載入必須使用 grunt.loadNpmTasks 函式,並且註冊任務別名。

上面介紹的結構合起來如下:

module.exports = function(grunt) {

  grunt.initConfig({ /* ... tasks configuration ... */ });
  grunt.loadNpmTasks('grunt-plugin-name');
  grunt.registerTask('default', ['firstTask', 'secondTask', ...]);

};

配置JSHint(Configure JSHint)

JSHint檢查JavaScript程式碼中潛在的問題和錯誤。他被設計成可配置的,並且有合理的預設值。

我們將使用 grunt-contrib-jshint 外掛,grunt-contrib開頭的外掛都是有Grunt官方維護的,如果你建立自己的外掛千萬不要以次開頭。

安裝外掛(Install the Plugin)

開啟命令列,在專案根目錄執行 npm install grunt-contrib-jshint --save-dev。將會新增外掛到package.json檔案的開發依賴,並且安裝到本地Npm倉庫。

JSHint引數(JSHint Options)

grunt-contrib-jshint外掛的引數和JSHint一樣。完整的引數列表可以訪問JSHint的文件頁面

JSHint 的引數 “eqeqeq” 會將 == 和 != 操作符報告為警告。預設是關閉的,因為這些操作符是合法的。但我建議你開啟它,因為嚴格相等比非嚴格相等更安全。

同時我建議你開啟trailing選項,將會對程式碼中結尾部的空白元素生成警告。結尾的空白在多行字串中會引起奇怪的問題。

每一個可選項都是布林值,設定為true將會開啟相應檢測。下面的例子開啟了eqeqeq和trailing選項:

options: {
  eqeqeq: true,
  trailing: true
} 

配置JSHint任務(Configure the JSHint Task)

grunt-contrib-jshint外掛的任務名字是“jshint”。我們將使用上一節中的配置選項,使它檢測位於src和test目錄下的全部JavaScript檔案。

JSHint的配置資訊必須寫在名為“jshint”的屬性內部。可以有兩個屬性,一個數引數(options)另一個是目標(target)。

目標可以在任何屬性內部,在這裡我們僅使用“target”。其必須包含待驗證的JavaScript檔案列表。檔案列表可以放在目標的src屬性中,可以使用**和*萬用字元。

有兩個自定義選項,將會驗證位於src和test目錄及其子目錄下的所有js檔案。

grunt.initConfig({
  jshint: {
    options: {
      eqeqeq: true,
      trailing: true
    },
    target: {
      src : ['src/**/*.js', 'test/**/*.js']
    }
  }
});

載入和註冊(Load and Register)

最後需要載入和註冊 grunt-contrib-jshint 任務:

grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('default', ['jshint']);

全部JSHint配置選項(Full JSHint Configuration)

目前為止完整的 Gruntfile.js 檔案如下:

module.exports = function(grunt) {

  grunt.initConfig({
    jshint: {
      options: {
        trailing: true,
        eqeqeq: true
      },
      target: {
        src : ['src/**/*.js', 'test/**/*.js']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.registerTask('default', ['jshint']);
};

拼接檔案(Concatenate Files)

js檔案一般是分散的(如果你的專案不是幾十行js的話),上線前我們必須將其打包進一個檔案,並新增版本號和專案名字。在檔案的開始位置應該包含庫名稱,版本,協議,構建時間和其他一些資訊。

例如,像下面這樣:

/*! gruntdemo v1.0.2 - 2013-06-04
 *  License: BSD */
var gruntdemo = function() {
  ...

我們將使用 grunt-contrib-concat 外掛完成拼接任務,並生成正確的註釋資訊。

安裝外掛(Install the Plugin)

和前面一樣,將外掛寫進package.json檔案的開發依賴裡,然後從Npm倉庫安裝到本地。

開啟命令列,執行如下命令:

npm install grunt-contrib-concat --save-dev

載入Package.json(Load Package.json)

首先從package.json檔案載入配置資訊,並儲存在pkg屬性。需要使用 grunt.file.readJSON 函式:

pkg: grunt.file.readJSON('package.json'),

現在pkg的值是一個物件,包含全部package.json的資訊。專案名被儲存在pkg.name屬性,版本被儲存在pkg.version。版權被儲存在pkg.license屬性等等。

生成頁頭資訊和 檔名(Compose Banner and File Name)

Grunt提供一套模版系統,我們可以使用它構建頁頭和檔名稱。模版可以在字串中嵌入JavaScript表示式,通過<%= expression %> 語法。Grunt計算表示式的值並替換模版中的表示式。

例如,模版中的 <%= pkg.name %> 將被替換為 pkg.name 的屬性值。如果屬性值是字串,模版的行為類似字串拼接 ...' + pkg.name + '...

模版中可以引用Grunt中的全部屬性。系統提供了一個非常有幫助的日期格式化函式。我們將使用 grunt.template.today(format) 函式生成當前的時間戳。

讓我們生成一個簡單的頁頭,包含專案名稱,版本號,版權和當前的日期。由於我們需要在 Uglify 任務中使用banner,所以我們將其儲存在變數中:

var bannerContent = '/*! <%= pkg.name %> v<%= pkg.version %> - ' +
                    '<%= grunt.template.today("yyyy-mm-dd") %> \n' +
                    ' *  License: <%= pkg.license %> */\n';

上面的模版生成如下的頁頭:

/*! gruntdemo v0.0.1 - 2013-06-04
 *  License: BSD */ 

專案的名稱和版本部分也需要在多處使用。將專案名和版本號拼在一起,儲存在一個變數中:

var name = '<%= pkg.name %>-v<%= pkg.version%>'; 

生成的名字如下:

gruntdemo-v0.0.1

配置目標和選項(Configure Target and Options)

target必須包含需要被拼接的檔案列表,和合並完成後輸出檔案的名字。target支援萬用字元和模版,所以我們使用前一節生成的模版:

target : {
  // 拼接src目錄下的所有檔案
  src : ['src/**/*.js'],
  // place the result into the dist directory,
  // name variable contains template prepared in
  // previous section
  dest : 'distrib/' + name + '.js'
}

concat外掛也可以通過banner屬性新增banner。由於上面我們已經將banner內容賦給bannerContent變數,所以我們僅需引入即可:

options: {
  banner: bannerContent
}

載入和註冊(Load and Register)

最後不要忘記從Npm載入 grunt-contrib-concat ,並且將其註冊到預設工作流:

grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['jshint', 'concat']);

完整的拼接配置(Full Concat Configuration)

這一節出示包含完整contat配置的Gruntfile.js檔案。

注意pkg屬性在傳遞給initConfig方法的引數中定義。我們不能把他放在其他地方,因為它讀取模版資訊,並且僅在initConfig方法的引數和grunt物件中有訪問模版的許可權。

module.exports = function(grunt) {
  var bannerContent = '... banner template ...';
  var name = '<%= pkg.name %>-v<%= pkg.version%>';

  grunt.initConfig({
    // pkg is used from templates and therefore
    // MUST be defined inside initConfig object
    pkg : grunt.file.readJSON('package.json'),
    // concat configuration
    concat: {
      options: {
        banner: bannerContent
      },
      target : {
        src : ['src/**/*.js'],
        dest : 'distrib/' + name + '.js'
      }
    },
    jshint: { /* ... jshint configuration ... */ }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.registerTask('default', ['jshint', 'concat']);
};

壓縮(Minify)

如果瀏覽器載入和解析大檔案,會使頁面載入變得緩慢。可能不是所有專案都會遇到這個問題,但對於移動端的app和使用非常多大型庫的大型web應用程式而言,是必須要考慮的。

因此,我們也將我們庫進行壓縮。壓縮會將輸入的檔案變小,通過去除空白元素,註釋,替換變數名稱等,但不會改變程式碼邏輯。

壓縮功能使用 grunt-contrib-uglify 外掛,其通過Grunt整合 UglifyJs。它通過uglify任務拼接和壓縮一組檔案。

源程式對映(Source Maps)

壓縮會使生成的檔案難於閱讀和除錯,所以我們通過生成源程式對映來簡化問題。

源程式對映是壓縮檔案和原始檔之間的紐帶。如果瀏覽器支援,瀏覽器除錯工具會顯示對人友好的原始檔,而不是壓縮檔案。僅有chrome和nightly版本的 firefox支援原始碼對映。你可以在HTML5 rocksTutsplus 上獲取更多資訊,我建議你看看阮一峰老師的這篇文章:http://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html

安裝外掛(Install the Plugin)

將外掛新增到package.json的開發依賴裡,並且安裝到本地Npm倉庫。

使用如下命令:

npm install grunt-contrib-uglify --save-dev

配置目標(Configure Target)

配置uglify任務目標的方式和concat任務類似。必須要包含待壓縮的javascript檔案列表和輸出檔案的名字。

支援萬用字元和模版,所以我們可以使用前面章節中的用到的模版:

target : {
  // use all files in src directory
  src : ['src/**/*.js'],
  // place the result into the dist directory,
  // name variable contains template prepared in
  // previous sub-chapter
  dest : 'distrib/' + name + '.min.js'
}

配置選項(Configure Options)

配置banner的方式和concat一樣——通過設定“banner”屬性並且支援模版。因此,我們可以重複使用前面章節中準備好的 bannerContent 變數。

通過“sourceMap”屬性生成原始檔對映。包含生成檔案的名字。此外,必須設定“sourceMapUrl”和“sourceMapRoot”屬性。前一個包含相對於uglified檔案到原始檔對映檔案的路徑,後一個包含是原始檔對映到原始檔的相對路徑。

通過bannerContent變數生成頁首,通過name變數生成原始檔對映檔案的名字:

options: {
  banner: bannerContent,
  sourceMapRoot: '../',
  sourceMap: 'distrib/'+name+'.min.js.map',
  sourceMapUrl: name+'.min.js.map'
}

載入和註冊(Load and Register)

最後一步是從Npm載入 grunt-contrib-uglify,並且新增到預設任務列表:

grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('default', ['jshint', 'concat', 'uglify']);

全部 Uglify 配置資訊(Full Uglify Configuration)

下面是包含完整uglify配置資訊的Gruntfile.js檔案:

module.exports = function(grunt) {
  var bannerContent = '... banner template ...';
  var name = '<%= pkg.name %>-v<%= pkg.version%>';  

  grunt.initConfig({
    // pkg must be defined inside initConfig object
    pkg : grunt.file.readJSON('package.json'),
    // uglify configuration
    uglify: {
      options: {
        banner: bannerContent,
        sourceMapRoot: '../',
        sourceMap: 'distrib/'+name+'.min.js.map',
        sourceMapUrl: name+'.min.js.map'
      },
      target : {
        src : ['src/**/*.js'],
        dest : 'distrib/' + name + '.min.js'
      }
    },
    concat: { /* ... concat configuration ... */ },
    jshint: { /* ... jshint configuration ... */ }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
};

最後的釋出檔案(Latest Release File)

最後要釋出的庫包括兩個檔案,並且都在名字中含有版本號。這會給想要自動下載每個新版本的人造成不必要的困難。

如果想要檢視是否釋出了新版本和新版本的名字,必須每次都要獲取和解析一個json檔案。如果每次都更改名稱,則必須更新下載指令碼。

因此我們將使用 grunt-contrib-copy 外掛建立無版本號的檔案。

安裝外掛(Install the Plugin)

將外掛新增進package.json的開發依賴,並且從Npm倉庫安裝到本地。

使用如下命令:

npm install grunt-contrib-copy --save-dev

配置外掛(Configure the Plugin)

copy配置資訊包括三個目標,分別對應三個釋出檔案。沒有配置選項,基本和前一個外掛配置過程一樣。

僅有一點不一樣,就是多工。每個任務包含一對 src/dest,待拷貝的名字和待建立的名字。

對前面的任務配置選項稍加修改。將所有檔名字放到變數中,以便可以重複使用:

module.exports = function(grunt) {
  /* define filenames */
  latest = '<%= pkg.name %>';
  name = '<%= pkg.name %>-v<%= pkg.version%>';

  devRelease = 'distrib/'+name+'.js';
  minRelease = 'distrib/'+name+'.min.js';
  sourceMapMin = 'distrib/source-map-'+name+'.min.js';

  lDevRelease = 'distrib/'+latest+'.js';
  lMinRelease = 'distrib/'+latest+'.min.js';
  lSourceMapMin = 'distrib/source-map-'+latest+'.min.js';

  grunt.initConfig({
    copy: {
      development: { // copy non-minified release file
        src: devRelease,
        dest: lDevRelease
      },
      minified: { // copy minified release file
        src: minRelease,
        dest: lMinRelease
      },
      smMinified: { // source map of minified release file
        src: sourceMapMin,
        dest: lSourceMapMin
      }
    },
    uglify: { /* ... uglify configuration ... */ },
    concat: { /* ... concat configuration ... */ },
    jshint: { /* ... jshint configuration ... */ }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'copy']);

單元測試(Unit Tests)

最後,配置 grunt.js 執行單元測試,測試最新發布的檔案。我們將使用 grunt-contrib-qunit 外掛實現目標。這個外掛將在無頭的 PhantomJS 例項中執行 QUnit 單元測試。

這個解決方案不能模擬不同瀏覽器和查詢全部 bug,但對於我們來說已經足夠了。如果想得到更好的配置,可以使用 js-test-driver 或 其他類似工具,然而,關於 js-test-dirver 的配置超出了本文的範圍。

準備測試用例(Prepare Tests)

Qunit 單元測試經常要執行 src 目錄裡的 JavaScript 檔案,由於測試是開發的一部分。如果你想測試剛剛釋出的拼接壓縮後的版本工作狀況,需要建立一個新的 QUnit HTML 檔案,並載入最後釋出的檔案。

下面是一個例子是 Qunit 的入口檔案:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>QUnit Example</title>
  <link rel="stylesheet" href="../libs/qunit/qunit.css">
</head>
<body>
  <div id="qunit"></div>
  <div id="qunit-fixture"></div>
  <script src="../libs/qunit/qunit.js"></script>

  <!-- Use latest versionless copy of current release -->
  <script src="../distrib/gruntdemo.min.js"></script>
  <script src="tests.js"></script>
</body>
</html>

安裝外掛(Install the Plugin)

將外掛新增進package.json 的開發者依賴中,並且將其安裝到本地 Npm 倉庫。

使用如下命令:

npm install grunt-contrib-qunit --save-dev

配置外掛(Configure Plugin)

配置 grunt-contrib-qunit 外掛和配置前面的任務如出一轍。由於我們使用預設的 Qunit 配置,所以可以省略選項屬性。不能忽略的是必須配置 target,指定全部的 Qunit HTML 檔案。

接下來指定位於測試目錄下的全部 HTML 檔案,及其子目錄應該執行 Qunit 測試:

grunt.initConfig({
  qunit:{
    target: {
      src: ['test/**/*.html']
    }
  },
  // ... all previous tasks ...
});

完整的 Grunt.js 檔案(Final Grunt.js File)

下面是完整的 Gruntfile.js 配置資訊:

module.exports = function(grunt) {
  var name, latest, bannerContent, devRelease, minRelease,
      sourceMap, sourceMapUrl, lDevRelease, lMinRelease,
      lSourceMapMin;

  latest = '<%= pkg.name %>';
  name = '<%= pkg.name %>-v<%= pkg.version%>';
  bannerContent = '/*! <%= pkg.name %> v<%= pkg.version %> - ' +
    '<%= grunt.template.today("yyyy-mm-dd") %> \n' +
    ' *  License: <%= pkg.license %> */\n';
  devRelease = 'distrib/'+name+'.js';
  minRelease = 'distrib/'+name+'.min.js';
  sourceMapMin = 'distrib/'+name+'.min.js.map';
  sourceMapUrl = name+'.min.js.map';

  lDevRelease = 'distrib/'+latest+'.js';
  lMinRelease = 'distrib/'+latest+'.min.js';
  lSourceMapMin = 'distrib/'+latest+'.min.js.map';

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    qunit:{
      target: {
        src: ['test/**/*.html']
      }
    },
    // configure copy task
    copy: {
      development: {
        src: devRelease,
        dest: lDevRelease
      },
      minified: {
        src: minRelease,
        dest: lMinRelease
      },
      smMinified: {
        src: sourceMapMin,
        dest: lSourceMapMin
      }
    },
    // configure uglify task
    uglify:{
      options: {
        banner: bannerContent,
        sourceMapRoot: '../',
        sourceMap: sourceMapMin,
        sourceMappingURL: sourceMapUrl
      },
      target: {
        src: ['src/**/*.js'],
        dest: minRelease
      }
    },
    // configure concat task
    concat: {
      options: {
        banner: bannerContent
      },
      target: {
        src: ['src/**/*.js'],
        dest: devRelease
      }
    },
    // configure jshint task
    jshint: {
      options: {
        trailing: true,
        eqeqeq: true
      },
      target: {
        src: ['src/**/*.js', 'test/**/*.js']
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-contrib-qunit');

  grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'copy', 'qunit']);
};

結論(Conclusion)

現在 Grunt.js 配置好了,並且可以使用了。我們的目標是使配置儘可能簡單,使用成對的 src/dest,萬用字元和模版。當然,Grunt.js 也提供其他更高階的選項

如果能夠自動下載和管理專案依賴的庫,會變得更美好。我發現兩個可行的解決方案,BowerEnder。我沒有試過他們,但都可以管理前端JavaScript包和其依賴。

文章有些長,拖了很久的文章終於翻譯完成了,我最近打算寫一本關於 Grunt 指南的書籍,會詳細講解如何構建一套前端自動化工具,如果你支援我的工作,那就給我捐助吧。

相關文章