Powrót do kategorii
Frontend
tagi
grunt-processhtml, grunt.js, optymalizacja, organizacja pracy, środowisko developerskie, środowisko pracy developera, środowisko produkcyjne,

Jak stworzyć środowisko developerskie i produkcyjne przy pomocy Grunt.js

Artur
Artur, 02/07/2015

Rozdzielenie projektu na wersję developerską (dev) oraz produkcyjną (dist) jest bardzo przydatnym zabiegiem. Umożliwia łatwe debugowanie błędów w wersji dev, a także porównanie rezultatów optymalizacji wersji dist.  Proces ten można zautomatyzować przy pomocy Grunt.js.

Oto etapy procesu:

  1. Instalacja pluginów,
  2. Pliki źródłowe (src),
  3. Środowisko developerskie (dev),
  4. Środowisko produkcyjne (dist),
  5. Podsumowanie.

Poniżej przedstawiam rozwinięcie każdego z nich.

1. Instalacja pluginów

Zakładam, że Grunt.js jest przynajmniej znany w podstawowym zakresie. W przeciwnym razie polecam przeczytać wprowadzenie do Grunt.js lub artykuł w, którym został opisany proces instalacji.

Wtyczki wykorzystane w przykładzie:

Można instalować każdy z osobna lub przygotować plik package.json z listą zależności:

{
  "name": "Env",
  "version": "0.1.0",
  "author": "Artur Bala",
  "private": true,
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-concat": "^0.5.1",
    "grunt-contrib-copy": "^0.8.0",
    "grunt-contrib-htmlmin": "^0.4.0",
    "grunt-contrib-uglify": "^0.9.1",
    "grunt-contrib-watch": "~0.6.1",
    "grunt-newer": "^1.1.0",
    "grunt-processhtml": "^0.3.7"
  }
}

i uruchomić komendę:

npm install

2. Pliki źródłowe (src)

Wyjściowa struktura plików:

root
|-- Gruntfile.js
|-- node_modules
|   |-- [...]
|-- package.json
|-- src
|   |-- js
|   |   |-- libs
|   |   |   |-- lib-1.js
|   |   |   |-- lib-2.js
|   |   |-- scripts.js
|   |-- template
|   |   |-- includes
|   |   |   |-- footer.html
|   |   |   |-- header.html
|   |   |-- index.html

W katalogu z plikami źródłowymi src  w powyższym przykładzie umieszczone są pliki .js oraz pliki szablonów używane przez grunt-processhtml.

W tym przykładzie w header.html nie ma nic nadzwyczajnego:

<!doctype html>

<html lang="en">

  <head>
    <meta charset="utf-8">

    <title>Test Env</title>
    
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
  </head>

  <body>

    <!--[if lte IE 8]>
      <p class="browsehappy">You are using an <strong>outdated</strong>
        browser. Please
        <a href="http://browsehappy.com/">upgrade your browser</a> to
        improve your experience.</p>
    <![endif]-->

    <div id="container" class="container">

      <div class="container-inner">

        <!-- Header -->
        
        <header id="header" class="header" role="banner">
          
          Header

        </header>
        <!-- end Header -->

W pliku index.html załączany jest header i footer:

        <!-- build:include header.html --><!-- /build -->

        <!-- Content -->

        <main id="content" class="content" role="main">

          <div class="wrap">

            Content

          </div>

        </main>
        <!-- end Content -->

        <!-- build:include footer.html --><!-- /build -->

Natomiast w footer.html w zależności od środowiska, załączone są wszystkie pliki .js lub jeden skompresowany:

      </div>

      <!-- Footer -->

      <footer id="footer" role="contentinfo">
        
        Footer

      </footer>
      <!-- end Footer -->

    </div>    

    <!-- Scripts -->

    <!-- build:template
    <% if (environment === 'dev') { %>
    <script src="assets/js/libs/lib-1.js"></script>
    <script src="assets/js/libs/lib-2.js"></script>
    <script src="assets/js/scripts.js"></script>
    <% } else { %>
    <script src="assets/js/scripts.min.js" defer></script>
    <% } %>
    /build -->
    <!-- end Scripts -->

  </body>

</html>

Cała konfiguracja znajduje się w pliku Gruntfile.js:

module.exports = function(grunt){
  'use strict';

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

    // Processhtml

    processhtml: {
      options: {
        process: true,
        recursive: true,
        includeBase: 'src/template/includes/'
      },
      dev: {
        options: {
          environment: 'dev'
        },
        files: [
          {
            expand: true,
            cwd: 'src/template/',
            src: ['*.html'],
            dest: 'dev/',
            ext: '.html'
          }
        ]
      },
      dist: {
        options: {
          environment: 'dist'
        },
        files: [
          {
            expand: true,
            cwd: 'src/template/',
            src: ['*.html'],
            dest: 'dist/',
            ext: '.html'
          }
        ]
      }
    },

    // Copy

    copy: {
      dev: {
        files: [
          {
            expand: true,
            cwd: 'src/js/',
            src: '**',
            dest: 'dev/assets/js/',
            filter: 'isFile'
          }
        ]     
      }
    },

    // HTML min

    htmlmin: {                                    
      dist: {                                     
        options: {                             
          removeComments: true,
          collapseWhitespace: true,
          minifyJS: true
        },
        files: {                                 
          'dist/index.html': 'dist/index.html'
        }
      }
    },

    // Concat
    concat: {
      dist: {
        src: [
          'src/js/libs/lib-1.js',
          'src/js/libs/lib-2.js',
          'src/js/scripts.js'
        ],
        dest: 'dist/assets/js/scripts.js',
      }
    },

    // Uglify

    uglify: {
      options: {
        preserveComments: 'some'
      },
      dist: {
        files: {
          'dist/assets/js/scripts.min.js': ['dist/assets/js/scripts.js']
        }
      }
    },

    // Watch
    watch: {
      js: {
        files: ['src/js/**/*.js'],
        tasks: ['copy:dev']
      },
      html: {
        files: ['src/template/**/*.html'],
        tasks: ['processhtml:dev']
      }
    }

  });

  grunt.loadNpmTasks('grunt-contrib-htmlmin');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-copy');
  grunt.loadNpmTasks('grunt-processhtml');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('dev', ['processhtml:dev', 'copy:dev', 'watch']);
  grunt.registerTask('dist', ['processhtml:dist', 'htmlmin', 'concat:dist', 'uglify:dist']);
  grunt.registerTask('default', ['dev']);

};

 3. Środowisko developerskie (dev)

Uruchomienie komendy:

grunt dev

lub z powodu tego, że „dev” jest domyślnym taskiem, również komendy:

grunt

spowoduje uruchomienie następujących procesów:

grunt.registerTask('dev', ['processhtml:dev', 'copy:dev', 'watch']);

 – processhtml:dev

      dev: {
        options: {
          environment: 'dev'
        },
        files: [
          {
            expand: true,
            cwd: 'src/template/',
            src: ['*.html'],
            dest: 'dev/',
            ext: '.html'
          }
        ]
      }

Odpowiada za przetworzenie szablonów HTML z katalogu templates i umieszczenie wynikowego pliku index.html w katalogu dev. Dzięki ustawieniu parametru environment na ‚dev’ i warunkowi w footer.html:

<!-- build:template
    <% if (environment === 'dev') { %>
    <script src="assets/js/libs/lib-1.js"></script>
    <script src="assets/js/libs/lib-2.js"></script>
    <script src="assets/js/scripts.js"></script>
    <% } else { %>
    <script src="assets/js/scripts.min.js" defer></script>
    <% } %>
    /build -->

ładowane są wszystkie 3 pliki.js w niepołączonej, nieskompresowanej postaci.

– copy:dev

Kopiuje pliki .js z src do dev.

– watch

Nasłuchuje zmian w plikach .js oraz plikach szablonów i uruchamia odpowiednio proces processhtml:dev lub copy:dev.

Wynikowa struktura plików:

root
|-- dev
|   |-- assets
|   |   |-- js
|   |   |   |-- libs
|   |   |   |   |-- lib-1.js
|   |   |   |   |-- lib-2.js
|   |   |   |-- scripts.js
|   |-- index.html
|-- Gruntfile.js
|-- node_modules
|   |-- [...]
|-- package.json
|-- src
|   |-- [..]

oraz podgląd z konsoli Chrome dla strony w wersji developerskiej:

Środowisko developerskie

4. Środowisko produkcyjne (dist)

Utworzenie wersji produkcyjnej realizowane jest przy pomocy komendy:

grunt dist

Uruchamiane są procesy:

grunt.registerTask('dist', ['processhtml:dist', 'htmlmin', 'concat:dist', 'uglify:dist']);

Analogicznie do wersji dev, pliki szablonów są przetwarzane w procesie processhtml:dist, z tym że wynikowa postać trafia do katalogu dist. W stopce ładowany jest tylko jeden plik scripts.min.js , zminimalizowany i złączony za pomocą procesów concat:dist oraz uglify:dist. Dodatkowo HTML zostaje zminimalizowany przez htmlmin.

Wynikowa struktura plików:

root
|-- dev
|   |-- [...] 
|-- dist
|   |-- assets
|   |   |-- js
|   |   |   |-- scripts.js
|   |   |   |-- scripts.min.js
|   |-- index.html
|-- Gruntfile.js
|-- node_modules
|   |-- [...]
|-- package.json
|-- src
|   |-- [..]

oraz podgląd z konsoli Chrome dla wersji produkcyjnej:

Środowisko produkcyjne

Podsumowanie

Wersja developerska projektu umożliwia w łatwy sposób debugowanie błędów, co w przypadku wersji produkcyjnej może być utrudnione przez zabiegi optymalizacyjne (takie jak np. minimalizacja JS, minimalizacja HTML, cache itp.). Mając dwie wersje można porównać także, w jakim stopniu optymalizacja przyniosła pomyślne efekty. W celu uproszczenia, przedstawiony przykład przedstawił wyłącznie dwa sposoby optymalizacji, bez uwzględnienia plików CSS, obrazków, cache itd. Jest to temat na osobny artykuł. 🙂

Podobne artykuły

Audyt UX (użyteczności) – Dlaczego warto go wykonać?

Jedna z dróg do podniesienia sprzedaży na Twojej stronie WWW

Wykorzystanie Redis 3 jako systemu cache’ującego w Zend Framework 2

Optymalizacja wydajności aplikacji opartych na Zend Framework 2 z wykorzystaniem Redis.