Niech Grunt wygeneruje indeks.html dla różnych konfiguracji

Próbuję użyć Grunt jako narzędzie do budowania mojej webapp.

Chcę mieć co najmniej dwie konfiguracje:

I. Development setup - Ładowanie skryptów z oddzielnych plików, bez konkatenacji,

Więc mój indeks.html wyglądałby tak:
<!DOCTYPE html>
<html>
    <head>
        <script src="js/module1.js" />
        <script src="js/module2.js" />
        <script src="js/module3.js" />
        ...
    </head>
    <body></body>
</html>

II. Production setup - załaduj moje Skrypty zminifikowane i skonkatenowane w jednym pliku,

Z indeksem.html:

<!DOCTYPE html>
<html>
    <head>
        <script src="js/MyApp-all.min.js" />
    </head>
    <body></body>
</html>

Pytanie brzmi, Jak mogę sprawić, by grunt zrobił te indeks.html zależy od konfiguracji kiedy uruchamiam grunt dev lub grunt prod?

A może kopię w złym kierunku i łatwiej byłoby zawsze wygenerować MyApp-all.min.js, ale umieścić w nim wszystkie moje Skrypty (konkatenowane) lub skrypt loadera, który asynchronicznie ładuje te skrypty z oddzielnych plików?

Jak to robicie?
Author: Dmitry Pashkevich, 2012-09-13

12 answers

Niedawno odkryłem te zadania zgodne z Grunt v0.4.0:

  • Grunt-preprocess

    Gruntowanie zadania wokół modułu preprocess npm.

  • Grunt-env

    Grunt zadanie zautomatyzować konfigurację środowiska dla przyszłych zadań.

Poniżej fragmenty z mojego Gruntfile.js.

ENV setup:

env : {

    options : {

        /* Shared Options Hash */
        //globalOption : 'foo'

    },

    dev: {

        NODE_ENV : 'DEVELOPMENT'

    },

    prod : {

        NODE_ENV : 'PRODUCTION'

    }

},

Preprocess:

preprocess : {

    dev : {

        src : './src/tmpl/index.html',
        dest : './dev/index.html'

    },

    prod : {

        src : './src/tmpl/index.html',
        dest : '../<%= pkg.version %>/<%= now %>/<%= ver %>/index.html',
        options : {

            context : {
                name : '<%= pkg.name %>',
                version : '<%= pkg.version %>',
                now : '<%= now %>',
                ver : '<%= ver %>'
            }

        }

    }

}

Zadania:

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

grunt.registerTask('dev', ['jshint', 'env:dev', 'clean:dev', 'preprocess:dev']);

grunt.registerTask('prod', ['jshint', 'env:prod', 'clean:prod', 'uglify:prod', 'cssmin:prod', 'copy:prod', 'preprocess:prod']);

I w /src/tmpl/index.html plik szablonu (na przykład):

<!-- @if NODE_ENV == 'DEVELOPMENT' -->

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js"></script>
    <script src="../src/js/foo1.js"></script>
    <script src="../src/js/foo2.js"></script>
    <script src="../src/js/jquery.blah.js"></script>
    <script src="../src/js/jquery.billy.js"></script>
    <script src="../src/js/jquery.jenkins.js"></script>

<!-- @endif -->

<!-- @if NODE_ENV == 'PRODUCTION' -->

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

    <script src="http://cdn.foo.com/<!-- @echo name -->/<!-- @echo version -->/<!-- @echo now -->/<!-- @echo ver -->/js/<!-- @echo name -->.min.js"></script>

<!-- @endif -->

Jestem pewien, że moja konfiguracja jest inna niż większość ludzi, a przydatność powyższego zależy od twojej sytuacji. Dla mnie, chociaż jest to niesamowity kawałek kodu, Yeoman grunt-usemin jest bardziej wytrzymały, niż osobiście potrzebuję.

Uwaga: ja właśnie odkryłem dzisiaj wyżej wymienione zadania, więc może mi zabraknąć funkcji i / lub mój proces może ulec zmianie w dół drogi. Na razie uwielbiam prostotę i funkcje, które mają do zaoferowaniagrunt-preprocess igrunt-env . :)


[[59]}styczeń 2014 Aktualizacja: / Align = "left" / ..

Kiedy opublikowałem tę odpowiedź, nie było wielu opcji Grunt 0.4.x, które oferowały rozwiązanie, które działało na moje potrzeby. Teraz, miesiące później, domyślam się, że jest więcej opcji, które mogłyby być lepsze niż to, co zamieściłem tutaj. podczas gdy ja jeszcze osobiście używam i lubię używać, ta technika dla moich budów , proszę, aby przyszli czytelnicy poświęcili czas na przeczytanie innych udzielonych odpowiedzi i zbadanie wszystkich opcji. jeśli znajdziesz lepsze rozwiązanie, napisz swoją odpowiedź tutaj.

[[59]} Luty 2014 Aktualizacja:

Nie jestem pewien, czy będzie to komuś pomocne, ale stworzyłem to repozytorium demo na Githubie , które pokazuje kompletną (i bardziej złożoną konfigurację) przy użyciu techniki opisanej powyżej.

 161
Author: mhulse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-23 01:24:57

Wymyśliłem własne rozwiązanie. Jeszcze nie dopracowany, ale chyba zmierzam w tym kierunku.

W esensji używam grunt.szablon.proces () generuje mój index.html z szablonu, który analizuje bieżącą konfigurację i tworzy listę moich oryginalnych plików źródłowych lub linki do pojedynczego pliku z minifikowanym kodem. Poniższy przykład dotyczy plików js, ale to samo podejście można rozszerzyć na css i każdy inny możliwy tekst pliki.

grunt.js:

/*global module:false*/
module.exports = function(grunt) {
    var   // js files
        jsFiles = [
              'src/module1.js',
              'src/module2.js',
              'src/module3.js',
              'src/awesome.js'
            ];

    // Import custom tasks (see index task below)
    grunt.loadTasks( "build/tasks" );

    // Project configuration.
    grunt.initConfig({
      pkg: '<json:package.json>',
      meta: {
        banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
          '<%= grunt.template.today("yyyy-mm-dd") %> */'
      },

      jsFiles: jsFiles,

      // file name for concatenated js
      concatJsFile: '<%= pkg.name %>-all.js',

      // file name for concatenated & minified js
      concatJsMinFile: '<%= pkg.name %>-all.min.js',

      concat: {
        dist: {
            src: ['<banner:meta.banner>'].concat(jsFiles),
            dest: 'dist/<%= concatJsFile %>'
        }
      },
      min: {
        dist: {
        src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
        dest: 'dist/<%= concatJsMinFile %>'
        }
      },
      lint: {
        files: ['grunt.js'].concat(jsFiles)
      },
      // options for index.html builder task
      index: {
        src: 'index.tmpl',  // source template file
        dest: 'index.html'  // destination file (usually index.html)
      }
    });


    // Development setup
    grunt.registerTask('dev', 'Development build', function() {
        // set some global flags that all tasks can access
        grunt.config('isDebug', true);
        grunt.config('isConcat', false);
        grunt.config('isMin', false);

        // run tasks
        grunt.task.run('lint index');
    });

    // Production setup
    grunt.registerTask('prod', 'Production build', function() {
        // set some global flags that all tasks can access
        grunt.config('isDebug', false);
        grunt.config('isConcat', true);
        grunt.config('isMin', true);

        // run tasks
        grunt.task.run('lint concat min index');
    });

    // Default task
    grunt.registerTask('default', 'dev');
};

index.js (the index task):

module.exports = function( grunt ) {
    grunt.registerTask( "index", "Generate index.html depending on configuration", function() {
        var conf = grunt.config('index'),
            tmpl = grunt.file.read(conf.src);

        grunt.file.write(conf.dest, grunt.template.process(tmpl));

        grunt.log.writeln('Generated \'' + conf.dest + '\' from \'' + conf.src + '\'');
    });
}

Wreszcie, index.tmpl, z logiką generacji:

<doctype html>
<head>
<%
    var jsFiles = grunt.config('jsFiles'),
        isConcat = grunt.config('isConcat');

    if(isConcat) {
        print('<script type="text/javascript" src="' + grunt.config('concat.dist.dest') + '"></script>\n');
    } else {
        for(var i = 0, len = jsFiles.length; i < len; i++) {
            print('<script type="text/javascript" src="' + jsFiles[i] + '"></script>\n');
        }
    }
%>
</head>
<html>
</html>

UPD.W przeciwieństwie do poprzednich części gry, nie jest on w stanie wykonać żadnego zadania, które nie jest w stanie wykonać. Generuje produkcyjną wersję indeksu.html z informacji w wersji rozwojowej indeksu.html, jak również inne ustawienia środowiska. Trochę wyrafinowany ale ciekawe do obejrzenia.

 34
Author: Dmitry Pashkevich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-15 22:58:27

Nie podoba mi się tutaj rozwiązanie (w tym to, które wcześniej podałem ) i oto dlaczego:

  • problem z najwyższą głosowaną odpowiedzią polega na tym, że musisz ręcznie zsynchronizować listę tagów skryptu po dodaniu/zmianie nazwy/usunięciu pliku JS.
  • problem z akceptowaną odpowiedzią jest to, że lista plików JS nie może mieć dopasowania wzorca. Oznacza to, że musisz zaktualizować go ręcznie w pliku Gruntfile.

Rozgryzłem jak aby rozwiązać oba te problemy. Skonfigurowałem moje zadanie grunt tak, że za każdym razem, gdy plik jest dodawany lub usuwany, znaczniki skryptu są automatycznie generowane, aby to odzwierciedlić. W ten sposób nie musisz modyfikować pliku html ani pliku grunt, gdy dodajesz/usuwasz / zmieniasz nazwy plików JS.

Podsumowując, jak to działa, Mam szablon html ze zmienną dla znaczników skryptu. Używam https://github.com/alanshaw/grunt-include-replace aby wypełnić tę zmienną. W dev mode, ta zmienna pochodzi ze wzoru globbingu wszystkich moich plików JS. Zadanie watch ponownie oblicza tę wartość po dodaniu lub usunięciu pliku JS.

Teraz, Aby uzyskać różne wyniki w trybie dev lub prod, wystarczy wypełnić tę zmienną inną wartością. Oto jakiś kod:

var jsSrcFileArray = [
    'src/main/scripts/app/js/Constants.js',
    'src/main/scripts/app/js/Random.js',
    'src/main/scripts/app/js/Vector.js',
    'src/main/scripts/app/js/scripts.js',
    'src/main/scripts/app/js/StatsData.js',
    'src/main/scripts/app/js/Dialog.js',
    'src/main/scripts/app/**/*.js',
    '!src/main/scripts/app/js/AuditingReport.js'
];

var jsScriptTags = function (srcPattern, destPath) {
    if (srcPattern === undefined) {
        throw new Error("srcPattern undefined");
    }
    if (destPath === undefined) {
        throw new Error("destPath undefined");
    }
    return grunt.util._.reduce(
        grunt.file.expandMapping(srcPattern, destPath, {
            filter: 'isFile',
            flatten: true,
            expand: true,
            cwd: '.'
        }),
        function (sum, file) {
            return sum + '\n<script src="' + file.dest + '" type="text/javascript"></script>';
        },
        ''
    );
};

...

grunt.initConfig({

    includereplace: {
        dev: {
            options: {
                globals: {
                    scriptsTags: '<%= jsScriptTags(jsSrcFileArray, "../../main/scripts/app/js")%>'
                }
            },
            src: [
                'src/**/html-template.html'
            ],
            dest: 'src/main/generated/',
            flatten: true,
            cwd: '.',
            expand: true
        },
        prod: {
            options: {
                globals: {
                    scriptsTags: '<script src="app.min.js" type="text/javascript"></script>'
                }
            },
            src: [
                'src/**/html-template.html'
            ],
            dest: 'src/main/generatedprod/',
            flatten: true,
            cwd: '.',
            expand: true
        }

...

    jsScriptTags: jsScriptTags

jsSrcFileArray to typowy wzór zgniatania plików. jsScriptTags pobiera jsSrcFileArray i łączy je razem ze znacznikami script po obu stronach. {[7] } to prefiks, który chcę umieścić w każdym pliku.

A oto jak wygląda HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Example</title>

</head>

<body>    
@@scriptsTags
</body>
</html>

Teraz, Jak widać w konfiguracji, generuję wartość tej zmiennej jako mocno zakodowany znacznik script, gdy jest uruchamiany w trybie prod. W trybie dev ta zmienna rozwinie się do takiej wartości:

<script src="../../main/scripts/app/js/Constants.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/Random.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/Vector.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/StatsData.js" type="text/javascript"></script>
<script src="../../main/scripts/app/js/Dialog.js" type="text/javascript"></script>
Daj znać, jeśli masz jakieś pytania.

PS: To jest szalona ilość kodu na coś, co chciałbym zrobić w każdej aplikacji js po stronie klienta. Mam nadzieję, że ktoś może przekształcić to w wtyczkę wielokrotnego użytku. Może tak zrobię. pewnego dnia.

 15
Author: Daniel Kaplan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:20

Od jakiegoś czasu zadaję sobie to samo pytanie i myślę, że ten plugin grunt można skonfigurować tak, aby robił to, co chcesz: https://npmjs.org/package/grunt-targethtml . implementuje warunkowe znaczniki html, które zależą od celu grunt.

 13
Author: Per Quested Aronsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-01 20:27:51

Szukałem prostszego, prostszego rozwiązania więc połączyłem odpowiedź z tego pytania:

Jak umieścić blok if else w pliku gruntfile.js

I wymyślił następujące proste kroki:

  1. Zachowaj dwie wersje swoich plików indeksowych zgodnie z listą i nazwij je rozwijaniem indeksów.html i indeks-prodokcja.html.
  2. Użyj poniższej logiki w swoim pliku Gruntfile.js ' s Concat / copy block dla Twojego indeksu.html plik:

    concat: {
        index: {
            src : [ (function() {
                if (grunt.option('Release')) {
                  return 'views/index-production.html';
                } else {
                  return 'views/index-development.html';
                }
              }()) ],
           dest: '<%= distdir %>/index.html',
           ...
        },
        ...
    },
    
  3. Uruchom 'grunt --Release', aby wybrać indeks-produkcji.plik html i zostawić flagę, aby mieć wersję deweloperską.

Brak nowych wtyczek do dodania lub konfiguracji i żadnych nowych zadań grunt.

 7
Author: Edward T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:40

To zadanie o nazwie scriptlinker wygląda na łatwy sposób dodawania skryptów w trybie dev. Prawdopodobnie możesz najpierw uruchomić zadanie concat, a następnie skierować je do skonkatenowanego pliku w trybie prod.

 5
Author: Daniel Kaplan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-19 17:43:34

Grunt-dom-munger czyta i manipuluje HTML za pomocą selektorów CSS. Ex. Czytaj tagi z twojego html. Usuń węzły, dodaj węzły i nie tylko.

Możesz użyć grunt-dom-munger, aby odczytać wszystkie pliki JS, które są połączone przez twój indeks.html, uglify them and then use grunt-dom-munger again to modify your index.html do linkowania tylko minifikowanego JS

 5
Author: brillout,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-21 15:54:55

Znalazłem wtyczkę grunt o nazwie grunt-dev-prod-switch. Wszystko, co robi, to komentowanie pewnych bloków, których szuka na podstawie opcji --env, którą przekazujesz gruntowi(chociaż ogranicza cię to do dev, prod I test).

Po skonfigurowaniu go tak, jak wyjaśnia tutaj, możesz uruchomić na przykład:

grunt serve --env=dev, i wszystko, co robi, to komentowanie bloków, które są owinięte przez

    <!-- env:test/prod -->
    your code here
    <!-- env:test/prod:end -->

I odkomentuje bloki, które są owinięte przez

    <!-- env:dev -->
    your code here
    <!-- env:dev:end -->

Działa również na javascript, używam służy do konfigurowania odpowiedniego adresu IP, z którym można się połączyć dla mojego API zaplecza. Bloki zmieniają się na

    /* env:dev */
    your code here
    /* env:dev:end */

W Twoim przypadku byłoby to tak proste:

<!DOCTYPE html>
<html>
    <head>
        <!-- env:dev -->
        <script src="js/module1.js" />
        <script src="js/module2.js" />
        <script src="js/module3.js" />
        ...
        <!-- env:dev:end -->
        <!-- env:prod -->
        <script src="js/MyApp-all.min.js" />
        ...
        <!-- env:prod:end -->
    </head>
    <body></body>
</html>
 5
Author: anonymous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-08 13:00:59

Grunt-bake to fantastyczny skrypt grunt, który byłby świetny tutaj. Używam go w moim skrypcie jqm auto build.

Https://github.com/imaginethepoet/autojqmphonegap

Spójrz na moje chrząkanie.plik coffee:
bake:
    resources: 
      files: "index.html":"resources/custom/components/base.html"
To wygląda na wszystkie pliki w bazie.html i wysysa je w celu utworzenia indeksu.html działa fantastycznie dla aplikacji wielostronicowych (phonegap). Pozwala to na łatwiejszy rozwój, ponieważ wszyscy deweloperzy nie pracują nad jedną długą, jednostronicową aplikacją (zapobiegając wielu konfliktom checkins). Zamiast tego możesz podzielić strony i pracować na mniejszych fragmentach kodu i skompilować do pełnej strony za pomocą polecenia watch.

Piec odczytuje szablon z bazy.html i wstrzykuje strony HTML komponentu na zegarku.

<!DOCTYPE html>

JQuery Mobile Demo

App.initialize();

<body>
    <!--(bake /resources/custom/components/page1.html)-->
    <!--(bake /resources/custom/components/page2.html)-->
    <!--(bake /resources/custom/components/page3.html)-->
</body>

Możesz pójść o krok dalej i dodać zastrzyki na swoich stronach dla "menu ""wyskakujące okienka" itp., dzięki czemu można naprawdę podzielić strony na mniejsze, łatwe do zarządzania komponenty.

 4
Author: imaginethepoet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-16 19:47:46

Użyj kombinacji wiredep https://github.com/taptapship/wiredep i usemin https://github.com/yeoman/grunt-usemin aby grunt zajął się tymi zadaniami. Wiredep doda zależności jeden plik skryptu na raz, a usemin połączy je wszystkie w jeden plik do produkcji. Można to osiągnąć za pomocą tylko kilku komentarzy html. Na przykład, moje Pakiety bower są automatycznie dołączane i dodawane do html, gdy uruchamiam bower install && grunt bowerInstall:

<!-- build:js /scripts/vendor.js -->
<!-- bower:js -->
<!-- endbower -->
<!-- endbuild -->
 4
Author: Scottux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-09 19:57:08

Rozważmy proceshtml . Pozwala na zdefiniowanie wielu "celów" dla buildów. Komentarze są używane do warunkowego włączenia lub wykluczenia materiału z HTML:

<!-- build:js:production js/app.js -->
...
<!-- /build -->

Staje się

<script src="js/app.js"></script>

To nawet pretenduje do robienia takich fajnych rzeczy (patrz README):

<!-- build:[class]:dist production -->
<html class="debug_mode">
<!-- /build -->

<!-- class is changed to 'production' only when the 'dist' build is executed -->
<html class="production">
 2
Author: dat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-20 19:39:02

Ta odpowiedź nie jest dla noobów!

Użyj Nefrytowego szablonu ... przekazywanie zmiennych do szablonu Jade jest standardowym przypadkiem użycia bog

Używam grunt (grunt-contrib-jade), ale nie musisz używać grunt. Wystarczy użyć standardowego modułu npm jade.

Jeśli używasz grunt to Twój gruntfile chciałby coś takiego ...

jade: {
    options: {
      // TODO - Define options here
    },
    dev: {
      options: {
        data: {
          pageTitle: '<%= grunt.file.name %>',
          homePage: '/app',
          liveReloadServer: liveReloadServer,
          cssGruntClassesForHtmlHead: 'grunt-' + '<%= grunt.task.current.target %>'
        },
        pretty: true
      },
      files: [
        {
          expand: true,
          cwd: "src/app",
          src: ["index.jade", "404.jade"],
          dest: "lib/app",
          ext: ".html"
        },
        {
          expand: true,
          flatten: true,
          cwd: "src/app",
          src: ["directives/partials/*.jade"],
          dest: "lib/app/directives/partials",
          ext: ".html"
        }
      ]
    }
  },

Możemy teraz łatwo uzyskać dostęp do danych przekazanych przez Grunta w szablonie Jade.

Podobnie jak podejście stosowane przez Modernizr, ustawiłem klasę CSS na znacznik HTML zgodnie z wartością zmiennej przekazanej i może używać logiki JavaScript stamtąd na podstawie tego, czy klasa CSS jest obecna, czy nie.

Jest to świetne, jeśli używasz Angular, ponieważ możesz zrobić ng-if, aby uwzględnić elementy na stronie w oparciu o to, czy klasa jest obecna.

Na przykład, mogę dołączyć skrypt, jeśli klasa jest obecna ...

(na przykład, mogę włączyć live reload script w dev, ale nie w produkcji)

<script ng-if="controller.isClassPresent()" src="//localhost:35729/livereload.js"></script> 
 1
Author: danday74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-30 10:27:43