Ako integrovať Visual Studio 2013 s Grunt-om - časť 3

DOWNLOAD

Grunt less, csslint, jshint, ...

V predošlej časti sme úspešne integrovali Grunt s build procesom vo Visual Studiu a ukázali sme si, ako volať rôzne sady taskov v závislosti od zvolenej build konfigurácie vo Visual Studiu. Build task, ktorý sme implementovali však okrem výpisu do konzoly nič nerobil. Teraz si doplníme náš projekt o ďalšie súbory a zároveň aj grunt tasky, ktoré budú s týmito súbormi pri builde pracovať a ukážeme si, ako výstupy týchto taskov použiť priamo v našom MVC projekte.

Čo by to bolo za webovú aplikáciu bez css-ka? Začneme teda s ním. Respektíve začneme s less. Do projektu pridáme nový adresár less. V tomto adresári vytvoríme nový súbor site.less a nastavíme v okne Properties jeho Build Action na None. Keby sme ponechali Build Action na Content, tieto súbory by sa stali súčasťou aplikácie aj pri jej publikovaní, čo však nechceme, keďže to čo reálne potrebujeme je css súbor, ktorý vznike transformáciou zdrojových less súborov.

@font: normal normal 400 12px Tahoma, sans-serif;

html
, body {
    margin
: 0;
    padding
: 0;
    width
: 100%;
    height
: 100%;
    overflow
: hidden;
    font
: @font;
    
    .map 
{
        width
: 100%;
        height
: 100%;
    
}
}
site.less

Pre transformáciu less na css použijeme grunt task grunt-contrib-less. Pre validáciu transformovaných css použijeme task grunt-contrib-csslint a na výslednú minifikáciu použijeme task grunt-contrib-cssmin. Doplníme teda súbor package.json o referenciu na tieto balíčky:

{
  
// ...
  
"devDependencies": {
    
"grunt""^1.0.1",
    
"grunt-contrib-less""^1.2.0",
    
"grunt-contrib-csslint""^1.0.0",
    
"grunt-contrib-cssmin""^1.0.1"
  
}
}
Referencie na balíčky pre prácsu s less a css v package.json

Stiahnutie balíčkov prebehne automaticky počas buildu vďaka target-u InstallNpmPackagesTarget. Teraz doplníme gruntfile.js o načítanie vyššie uvedených taskov:

grunt.loadNpmTasks("grunt-contrib-less");
grunt.loadNpmTasks("grunt-contrib-csslint");
grunt.loadNpmTasks("grunt-contrib-cssmin");
Načítanie less a css grunt taskov

Upravíme build task tak, aby pri builde v DEBUG móde prebehla less transformácia a validácia css a pri builde v RELEASE móde prebehla navyše aj minifikácia css súborov:

grunt.registerTask("build"function (target) {
    
// ...
    
switch (target.toUpperCase()) {
        
case "DEBUG":
            tasks.push(
"less");
            
tasks.push("csslint");
            break;
        case 
"RELEASE":
            tasks.push(
"less");
            
tasks.push("csslint");
            
tasks.push("cssmin");
            break;
        default
:
            
// Unknown build configuration
            
grunt.fail.fatal("Unknown build configuration '" + target + "'");
            break;
    
}
    
// ...
});
Konfigurácia volania less a css grunt taskov pri builde

Aby vyššie uvedené tasky zbehli, musíme ešte v rámci gruntfile.js doplniť do volania grunt.initConfig konfiguráciu pre jednotlivé tasky:

grunt.initConfig({
    less: {
        options: {
            paths: [
"less"],
            strictMath: 
false
        
},
        src: {
            files: {
                
"css/site.css""less/site.less"
            
}
        }
    },
    csslint: {
        options: {
        },
        src: [
            
"css/**/*.css",
            
"!css/**/*.min.css"
        
]
    },
    cssmin: {
        options: {
        },
        build: {
            files: [{
                expand: 
true,
                cwd: 
"css",
                src: [
"*.css""!*.min.css"],
                dest: 
"css",
                ext: 
".min.css"
            
}]
        }
    },
})
;
Konfigurácia grunt taskov less, csslint a cssmin

Ak teraz spustíme rebuild projektu v adresárovej štruktúre projektu by mal vzniknúť podadresár css a v ňom súbor site.css, pri builde s konfiguráciou DEBUG. Pri builde s konfiguráciou RELEASE by mal vzniknúť aj súbor site.min.css. Tieto súbory nebudeme pridávať do projektu, keďže z nášho pohľadu to nie sú zdrojové súbory, ale výstupné súbory. Rovnako tak ich netreba pridávať do source controll-u (ak nejaký používame).

Výstupné css súbory v Solution Explorer-i
Výstupné css súbory v Solution Explorer-i

Ak by náš zdrojový less súbor obsahoval nejakú chybu, prípadne výsledné css by neprešlo css validáciou, príslušná chyba by sa zobrazila v Build Output okne a build by sa zastavil. Napriek tomu, že sme vygenerované súbory nepridali do projektu, vzniknú počas buildu a budu prítomné na disku, a preto na ne môžeme pridať odkaz do nášho view-u Index:

<!DOCTYPE html>
<html lang="en">
<head>
    
<meta charset="utf-8" />
    <
title>GruntMvcDemo</title>
    
<link rel="stylesheet" href="@Url.Content("~/css/site.css")" />
</
head>
<body>
    
<h1>GruntMvcDemo</h1>
</body>
</html>
Referencia na vygenerovaný css súbor v Index.cshtml

Treba ešte vyriešiť jeden malý problém, a to ten, že v DEBUG-u chceme používať súbor site.css, ale v RELEASE chceme používať súbor site.min.css. Spôsobov ako vyriešiť túto situáciu je niekoľko. Ja použijem ten, že rozšírim objekt System.Web.Mvc.HtmlHelper o extension metódu IsDebug, ktorá vráti true ak je nastavený prepínač DEBUG, inak vráti false. Do solution pridáme teda nový súbor HtmlHelperExtensions.cs:

using System.Web.Mvc;

namespace 
GruntMvcDemo.Web.Mvc
{
    
public static class HtmlHelperExtensions
    {
        
public static bool IsDebug(this HtmlHelper @this)
        {
            
#if DEBUG
                
return true;
            #else
                return false;
            #endif
        
}
    }
}
Extension metóda IsDebug

a následne upravíme náš Index view:

<!DOCTYPE html>
<html lang="en">
<head>
    
<meta charset="utf-8" />
    <
title>GruntMvcDemo</title>
    @if (Html.IsDebug())
    {
        
<link rel="stylesheet" href="@Url.Content("~/css/site.css")" />
    }
    else
    {
        <
link rel="stylesheet" href="@Url.Content("~/css/site.min.css")" />
    }
</
head>
<body>
    
<h1>GruntMvcDemo</h1>
</body>
</html>
Referencia na vygenerované css súbory podľa zvolenej konfigurácie v Index.cshtml

Čo s javascript-om? Pripravíme si jednoduchú aplikáciu (app.js), ktorá bude používať knižnice: require.js, require.js modul domReady a jQuery. Všetky tieto knižnice uložíme v rámci projektovej štruktúry do adresára /js/libs a pridáme ich do projektu.

V adresári /js vytvoríme nový javascriptový súbor app.js:

define([
    
"module",
    
"jquery",
    
"domReady!"
], function (module, $) {
    
// Handle na globalny window objekt
    
var global (function () { return this; })();

    
// Konstruktor
    
var Application = function (args) {
        args 
args || {};

        this
.environment args.environment || "";
    
};

    
// Nastartuje aplikaciu
    
Application.prototype.start = function () {
        $(
".map").html("app is running:" this.environment);
    
};

    
// Vytvorime instanciu aplikacie a odlizime si ju 
    // do globalnej premennej app
    
var app global.app = new Application(module.config());

    
// Spustime aplikaciu
    
app.start();
});
Obsah súboru app.js

Aplikácia nerobí nič špeciálne - v konštruktore si odloží do premennej this.environment názov prostredia v ktorom je spustená, v metóde start pomocou jQuery nastaví obsah elementu s atribútom class="map". Ako vstupný parameter sa do konštruktora objektu Application posiela výsledok volania module.config() - konfigurácia modulu app. Za týmto účelom pridáme do adresára /js nový súbor config.js:

require({
    paths: {
        jquery: 
"libs/jquery",
        domReady: 
"libs/domReady"
    
},
    shim: {
        
"jquery": {
            exports: 
"jQuery"
        
}
    },
    config: {
        
"app": {
            environment: 
"DEBUG"
        
}
    }
}, [
"app"]);
Obsah súboru config.js

Vyššie uvedený súbor predstavuje konfiguráciu pre knižnicu require.js, kde v rámci property config je aj konfigurácia modulu app. Viac informácií o tom ako konfigurovať knižnicu require.js je možné nájsť tu. Aby sme ešte zfunkčnili našu aplikáciu, upravíme ešte raz Index view:

<!DOCTYPE html>
<html lang="en">
<head>
    
<meta charset="utf-8" />
    <
title>GruntMvcDemo</title>
    @if (Html.IsDebug())
    {
        
<link rel="stylesheet" href="@Url.Content("~/css/site.css")" />
    }
    else
    {
        <
link rel="stylesheet" href="@Url.Content("~/css/site.min.css")" />
    }
</
head>
<body>
    @*
<h1>GruntMvcDemo</h1>*@
    
<div class="map"></div>
    
<script data-main="@Url.Content("~/js/config")" src="@Url.Content("~/js/libs/require.js")"></script>
</body>
</html>
Pridanie referencie na app.js v Index.cshtml

Popis predošlej ukážky nie je úplne presný, keďže nejde o pridanie referencie na app.js ale na require.js javascript. Knižnica require.js však automaticky po načítaní prevezme obsah atribútu data-main a dotiahne do prehliadača tento skript - modul. Ten predstavuje konfiguráciu, kde na jej konci je povedané, že sa má dotiahnuť modul app. To, že prečo je to možno na prvý pohľad takto komplikovane spravené má svoj význam. Neskôr si totiž ukážeme ako pomocou grunt tasku pri builde použiť inú konfiguráciu pre aplikáciu v závislosti od zvolenej konfigurácie vo Visual Studiu.

Teraz sa však sústreďme na fakt, že v našej aplikácii máme nejaké javascript-y a cheme ich pri builde skontrolovať na čo využijeme grunt task grunt-contrib-jshint. Doplníme teda súbor package.json o referenciu na tento balíček:

{
  
// ...
  
"devDependencies": {
    
"grunt""^1.0.1",
    
"grunt-contrib-clean""^1.0.0",
    
"grunt-contrib-csslint""^1.0.0",
    
"grunt-contrib-cssmin""^1.0.1",
    
"grunt-contrib-jshint""^1.0.0"
  
}
}
Referencia na balíček pre validáciu javascript súborov

Teraz doplníme gruntfile.js o načítanie vyššie uvedeného tasku:

grunt.loadNpmTasks("grunt-contrib-jshint");
Načítanie grunt tasku grunt-contrib-jshint

Doplníme konfiguráciu pre jshint task do volania grunt.initConfig:

grunt.initConfig({
    
// ...
    
jshint: {
        options: {
        
// V kode sa moze nachadzat prikaz 'debugger' 
            
debug: true,
            globals: {
                jQuery: 
true
            
}
        },
        src: [
        
// Skontroluj gruntfile.js
            
"gruntfile.js",
        
// Skontroluj vsetky subory s 
        // priponou *.js v adresari js
            
"js/**/*.js",
        
// Vynechaj vsetky subory s priponou 
        // *.js v podadresari libs
            
"!js/libs/*.js"
        
]
    },
})
;
Konfigurácia tasku jshint

V callback funkcii pre náš build task doplníme volanie tasku jshint pre konfiguráciu DEBUG a aj RELEASE, s tým rozdielom, že ak sa builduje aplikácia v RELEASE móde, dynamicky prestavíme prepínač jshint.options.debug na hodnotu false. V móde DEBUG je ešte ok, ak sa v kóde nachádza debugger príkaz, pretože programátor potrebuje povedzme debuggovať nejakú funkciu, ale pre RELEASE mód by kód už nemal obsahovať tieto príkazy:

grunt.registerTask("build"function (target) {
    
// ...
    
switch (target.toUpperCase()) {
        
case "DEBUG":
            tasks.push(
"less");
            
tasks.push("csslint");
            
tasks.push("jshint");
            break;
        case 
"RELEASE":
            grunt.config(
"jshint.options.debug"false);

            
tasks.push("less");
            
tasks.push("csslint");
            
tasks.push("cssmin");
            
tasks.push("jshint");
            break;
        default
:
            
// Unknown build configuration
            
grunt.fail.fatal("Unknown build configuration '" + target + "'");
            break;
    
}
    
// ...
});
Volanie tasku jshint v tasku build

Ak by sme napríklad doplnili volanie debugger do súboru app.js a spustili rebuild projektu v móde RELEASE, build by sa zastavil na chybe Forgotten 'debugger' statement?.

Už sme skoro v cieli. Ešte si ukážame ako volať vlastný grunt task pri publikovaní aplikácie priamo z Visual Studia. Ale to až nabudúce.

Publikované Monday, May 09, 2016 3:02 PM xxxmatko

Komentáre

# Ako integrovať Visual Studio 2013 s Grunt-om - časť 4 final

Monday, May 30, 2016 8:36 AM xxxmatko blog

DOWNLOAD V predošlej časti sme si ukázali ako pri builde aplikácie volať rôzne Grunt tasky v závislosti