web-dev-qa-db-fra.com

Demander à Grunt de générer index.html pour différentes configurations

J'essaie d'utiliser Grunt comme outil de construction pour mon application Web.

Je veux avoir au moins deux configurations:

I. Configuration du développement - charge les scripts à partir de fichiers séparés, sans concaténation,

donc mon index.html ressemblerait à quelque chose comme:

<!DOCTYPE html>
<html>
    <head>
        <script src="js/module1.js" />
        <script src="js/module2.js" />
        <script src="js/module3.js" />
        ...
    </head>
    <body></body>
</html>

II. Configuration de la production - charger mes scripts minifiés et concaténés dans un seul fichier,

avec index.html en conséquence:

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

La question est, comment puis-je faire grunt faire ces index.html en fonction de la configuration lorsque je lance grunt dev ou grunt prod?

Ou peut-être que je creuse dans la mauvaise direction et qu'il serait plus facile de toujours générer MyApp-all.min.js, mais y mettre soit tous mes scripts (concaténés), soit un script de chargement qui les charge de manière asynchrone à partir de fichiers séparés?

Comment faites-vous les gars?

207
Dmitry Pashkevich

J'ai mis au point ma propre solution. Pas encore fini, mais je pense que je vais aller dans cette direction.

En fait, j'utilise grunt.template.process () pour générer mon _index.html_ à partir d'un modèle qui analyse la configuration actuelle et génère soit une liste de mes fichiers source d'origine, soit des liens vers un fichier. fichier unique avec code minifié. L'exemple ci-dessous concerne les fichiers js, mais la même approche peut être étendue à css et à tout autre fichier texte possible.

_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 + '\'');
    });
}
_

Enfin, index.tmpl, avec une logique de génération intégrée:

_<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. Découvert que Yeoman , qui est basé sur le grognement, possède une fonction intégrée semin = tâche qui s’intègre au système de construction de Yeoman. Il génère une version de production de index.html à partir des informations de la version de développement de index.html, ainsi que d'autres paramètres d'environnement. Un peu sophistiqué mais intéressant à regarder.

34
Dmitry Pashkevich

J'ai récemment découvert ces tâches compatibles Grunt v0.4.0:

  • grunt-preprocess

    Grunt tâche autour du module de pré-traitement npm.

  • grunt-env

    Grunt tâche pour automatiser la configuration de l'environnement pour les tâches futures.

Vous trouverez ci-dessous des extraits de mon Gruntfile.js.

Configuration ENV:

env : {

    options : {

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

    },

    dev: {

        NODE_ENV : 'DEVELOPMENT'

    },

    prod : {

        NODE_ENV : 'PRODUCTION'

    }

},

Prétraitement:

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 %>'
            }

        }

    }

}

Les tâches:

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']);

Et dans le fichier de modèle /src/tmpl/index.html (par exemple):

<!-- @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 -->

Je suis sûr que ma configuration est différente de celle de la plupart des gens, et l’utilité de ce qui précède dépend de votre situation. Pour moi, bien que ce soit un morceau de code génial, le Yeoman grunt-usemin est plus robuste que ce dont j'ai personnellement besoin.

REMARQUE: je viens de découvrir les tâches énumérées ci-dessus aujourd'hui. Il se peut donc que je manque une fonctionnalité et/ou mon processus peut changer sur la route. Pour l'instant, j'adore la simplicité et les fonctionnalités qui grunt-preprocess et grunt-env = avoir à offrir. :)


Mise à jour de janvier 2014:

Motivé par un vote négatif ...

Lorsque j'ai posté cette réponse, il n'y avait pas beaucoup d'options pour Grunt 0.4.x qui offrait une solution qui répondait à mes besoins. Maintenant, des mois plus tard, je suppose qu’il ya plus d’options que pourraient être meilleures que celles que j’ai publiées ici. Alors que j'utilise toujours personnellement cette technique pour mes versions et que j'aime bien l'utiliser, je demande aux futurs lecteurs de prendre le temps de lire les autres réponses données et recherche toutes les options. Si vous trouvez une meilleure solution, merci de poster votre réponse ici.

Mise à jour de février 2014:

Je ne suis pas sûr que cela puisse aider quelqu'un, mais j'ai créé ce dépôt de démonstration sur GitHub qui montre une configuration complète (et plus complexe) en utilisant la (les) technique (s) utilisée (s). avons décrit ci-dessus.

161
mhulse

Je n'aime pas les solutions ici (y compris celle que j'ai donnée précédemment ) et voici pourquoi:

  • Le problème avec la réponse la plus votée est que vous devez synchroniser manuellement la liste des balises de script lorsque vous ajoutez/renommez/supprimez un fichier JS.
  • Le problème avec la réponse acceptée est que votre liste de fichiers JS ne peut pas avoir de correspondance de modèle. Cela signifie que vous devez le mettre à jour manuellement dans le fichier Grunt.

J'ai compris comment résoudre ces deux problèmes. J'ai configuré ma tâche Grunt de sorte que chaque fois qu'un fichier est ajouté ou supprimé, les balises de script soient automatiquement générées pour refléter cela. Ainsi, vous n'avez pas besoin de modifier votre fichier html ou votre fichier grunt lorsque vous ajoutez, supprimez ou renommez vos fichiers JS.

Pour résumer comment cela fonctionne, j'ai un modèle HTML avec une variable pour les balises de script. J'utilise https://github.com/alanshaw/grunt-include-replace pour renseigner cette variable. En mode dev, cette variable provient d'un schéma de globalisation de tous mes fichiers JS. La tâche de surveillance recalcule cette valeur lorsqu'un fichier JS est ajouté ou supprimé.

Maintenant, pour obtenir des résultats différents en mode dev ou prod, vous devez simplement renseigner cette variable avec une valeur différente. Voici un code:

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 est votre motif de globalisation de fichier grunt typique. jsScriptTags prend la jsSrcFileArray et la concatène avec les balises script des deux côtés. destPath est le préfixe que je veux sur chaque fichier.

Et voici à quoi ressemble le code HTML:

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

</head>

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

Comme vous pouvez le constater dans la configuration, je génère la valeur de cette variable sous forme de balise script codée en dur lorsqu'elle est exécutée en mode prod. En mode dev, cette variable développera une valeur comme celle-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>

Faites moi savoir si vous avez des questions.

PS: C’est une quantité folle de code pour quelque chose que je voudrais faire dans chaque application JS côté client. J'espère que quelqu'un pourra transformer cela en un plugin réutilisable. Peut-être que je le ferai un jour.

15
Daniel Kaplan

Je me pose la même question depuis un moment et je pense que ce plugin grunt pourrait être configuré pour faire ce que vous voulez: https://npmjs.org/package/grunt-targethtml . Il implémente des balises HTML conditionnelles, qui dépendent de la cible Grunt.

13

Je recherchais une solution plus simple et plus simple, j'ai donc combiné la réponse à cette question:

Comment placer if block dans gruntfile.js

et est venu avec les étapes simples suivantes:

  1. Conservez deux versions de vos fichiers d'index comme vous l'avez indiqué et nommez-les index-development.html et index-prodoction.html.
  2. Utilisez la logique suivante dans le bloc concat/copy de votre Gruntfile.js pour votre fichier index.html:

    concat: {
        index: {
            src : [ (function() {
                if (grunt.option('Release')) {
                  return 'views/index-production.html';
                } else {
                  return 'views/index-development.html';
                }
              }()) ],
           dest: '<%= distdir %>/index.html',
           ...
        },
        ...
    },
    
  3. lancez 'grunt --Release' pour choisir le fichier index-production.html et laissez la version de développement désactivée.

Pas de nouveaux plugins à ajouter ou à configurer et pas de nouvelles tâches grunt.

8
Edward T

Cette tâche Grunt nommée scriptlinker ressemble à un moyen simple d’ajouter les scripts en mode dev. Vous pourriez probablement d'abord exécuter une tâche de concatte, puis la diriger vers votre fichier concaténé en mode prod.

5
Daniel Kaplan

J'ai trouvé un plugin grunt appelé grunt-dev-prod-switch. Tout ce qu’il fait est de commenter certains blocs qu’il recherche en fonction d’une option --env que vous passez à grogner (bien que cela vous limite à dev, à prod et à test).

Une fois que vous l'avez configuré comme il est expliqué ici , vous pouvez exécuter par exemple:

grunt serve --env=dev, et tout ce qu'il fait est de commenter les blocs enveloppés par

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

et il va décommenter les blocs qui sont enveloppés par

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

Il fonctionne également sur javascript, je l’utilise pour configurer la bonne adresse IP à laquelle se connecter pour mon API backend. Les blocs changent juste pour

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

Dans votre cas, ce serait aussi simple que cela:

<!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
anonymous

grunt-dom-munger lit et manipule HTML avec des sélecteurs CSS. Ex. Lire les tags de votre HTML. Supprimer des nœuds, ajouter des nœuds, etc.

Vous pouvez utiliser grunt-dom-munger pour lire tous vos fichiers JS liés par votre index.html, les uglifier, puis utiliser à nouveau grunt-dom-munger pour modifier votre index.html afin de ne lier que le fichier JS minifié.

5
brillout

grunt-bake est un script grunt fantastique qui fonctionnerait très bien ici. Je l'utilise dans mon script de construction automatique JQM.

https://github.com/imaginethepoet/autojqmphonegap

Jetez un oeil à mon fichier grunt.coffee:

bake:
    resources: 
      files: "index.html":"resources/custom/components/base.html"

Cela examine tous les fichiers dans base.html et les aspire pour créer index.html, qui est fantastique pour les applications de plusieurs pages (phonegap). Cela facilite le développement car tous les développeurs ne travaillent pas sur une seule application d'une seule page (ce qui évite beaucoup de contrôles de conflits). Au lieu de cela, vous pouvez diviser les pages, travailler sur de plus petits morceaux de code et les compiler sur la page entière à l'aide d'une commande watch.

Bake lit le modèle à partir de base.html et injecte les pages HTML des composants sous surveillance.

<!DOCTYPE html>

démonstrations jQuery Mobile

app.initialize ();

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

Vous pouvez aller plus loin et ajouter des injections dans vos pages pour les "menus", les "popups", etc. afin que vous puissiez réellement diviser les pages en composants plus petits gérables.

4
imaginethepoet

Utilisez une combinaison de wiredep https://github.com/taptapship/wiredep et de usemin https://github.com/yeoman/grunt-usemin afin de pouvoir grogner prendre soin de ces tâches. Wiredep ajoutera vos dépendances un fichier de script à la fois et usemin les concaténera dans un seul fichier pour la production. Cela peut alors être accompli avec seulement quelques commentaires HTML. Par exemple, mes paquets bower sont automatiquement inclus et ajoutés au code HTML lorsque je lance bower install && grunt bowerInstall:

<!-- build:js /scripts/vendor.js -->
<!-- bower:js -->
<!-- endbower -->
<!-- endbuild -->
4
Scottux

Considérons processhtml . Il permet la définition de plusieurs "cibles" pour les générations. Les commentaires sont utilisés pour inclure ou exclure de manière conditionnelle des éléments du code HTML:

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

devient

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

Il est même supposé faire de chouettes choses comme ça (voir le fichier 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
dat

Cette réponse n'est pas pour les noobs!

Utiliser des modèles Jade ... le passage de variables à un modèle Jade est un cas d'utilisation standard

J'utilise grunt (grunt-contrib-jade) mais vous n'êtes pas obligé d'utiliser grunt. Il suffit d’utiliser le module standard jade npm.

Si vous utilisez grunt, votre fichier grunt aimerait quelque chose comme ...

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"
        }
      ]
    }
  },

Nous pouvons maintenant accéder facilement aux données transmises par grunt dans le modèle Jade.

Tout comme l'approche utilisée par Modernizr, j'ai défini une classe CSS sur la balise HTML en fonction de la valeur de la variable transmise et je peux utiliser la logique JavaScript à partir de là, selon que la classe CSS est présente ou non.

C'est très bien si vous utilisez Angular puisque vous pouvez faire inclure des éléments dans la page en fonction de la présence ou non de la classe.

Par exemple, je pourrais inclure un script si la classe est présente ...

(Par exemple, je pourrais inclure le script de recharge live dans dev mais pas dans la production)

<script ng-if="controller.isClassPresent()" src="//localhost:35729/livereload.js"></script> 
1
danday74