Have Grunt génère index.html pour différentes configurations

J'essaie d'utiliser Grunt comme un outil de construction pour mon webapp.

Je veux avoir au moins deux configurations:

I. Configuration de développement – charger des scripts à partir de fichiers séparés, sans concaténation,

Donc, mon index.html ressemblerait à:

<!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 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, faites-les 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 il serait plus facile de générer toujours MyApp-all.min.js mais mettez-le soit tous mes scripts (concaténés), soit un script de chargement qui stocke de manière asynchrone ces scripts à partir de fichiers distincts?

Comment faites-vous, les gars?

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

  • Grunt-preprocess

    Grunt sur le module de pré-traitement npm.

  • Grunt-env

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

Voici quelques 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 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 mon configuration est différente de la plupart des gens, et l'utilité de ce qui précède dépendra de votre situation. Pour moi, alors que c'est un code formidable, Yeoman grunt-usemin est plus robuste que ce que j'ai personnellement besoin.

REMARQUE: Je viens de découvrir les tâches listées ci-dessus aujourd'hui, donc je risque de manquer une fonctionnalité et / ou mon processus peut changer sur la route. Pour l'instant, j'aime la simplicité et les fonctionnalités que grunt-preprocess et grunt-env ont à offrir. 🙂


Jan 2014 mise à jour:

Motivé par un vote en baisse …

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 fonctionnait pour mes besoins. Maintenant, quelques mois plus tard, j'imagine qu'il y a plus d'options qui pourraient être meilleures que celles que j'ai publiées ici. Bien que j'utilise personnellement, et j'utilise, cette technique pour mes constructions , je demande aux futurs lecteurs de prendre le temps de lire les autres réponses et de rechercher toutes les options. Si vous trouvez une meilleure solution, veuillez poster votre réponse ici.

Mise à jour de février 2014:

Je ne sais pas si cela sera utile à tous, mais j'ai créé ce dépôt de démonstration sur GitHub qui montre une configuration complète (et plus complexe) à l'aide des techniques décrites ci-dessus.

J'ai trouvé ma propre solution. Pas encore polis, mais je pense que je vais aller dans cette direction.

En essence, j'utilise grunt.template.process () pour générer mon index.html partir d'un modèle qui analyse la configuration actuelle et produit soit une liste de mes fichiers sources d'origine ou des liens vers un seul fichier avec un 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 cuite dans:

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

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

  • Le problème avec la réponse votée la plus élevé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 une correspondance de motif. Cela signifie que vous devez le mettre à jour à la main dans Gruntfile.

J'ai compris comment résoudre ces deux problèmes. J'ai configuré ma tâche grincheuse afin que chaque fois qu'un fichier soit ajouté ou supprimé, les tags de script sont automatiquement générés pour refléter cela. De cette façon, vous n'avez pas besoin de modifier votre fichier html ou votre fichier grunt lorsque vous ajoutez / supprimez / 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 remplir cette variable. En mode dev, cette variable provient d'un modèle global 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 remplir 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 modèle typique de grognement. jsScriptTags prend le jsSrcFileArray et les concatène avec les étiquettes de script des deux côtés. destPath est le préfixe que je veux sur chaque fichier.

Et voici comment apparait le HTML:

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

Maintenant, comme vous pouvez le voir dans la configuration, je génère la valeur de cette variable comme une étiquette de script codée dur lorsqu'elle est exécutée en mode prod . En mode développeur, cette variable va se développer à 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 peut transformer cela en un plugin réutilisable. Peut-être que je vais un jour.

Je me pose la même question pendant un certain temps, 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 grungeuse.

Je cherchais une solution plus simple et directe, alors j'ai combiné la réponse de cette question:

Comment placer si autrement bloque dans gruntfile.js

Et a proposé les étapes suivantes suivantes:

  1. Gardez deux versions de vos fichiers d'index comme vous l'avez listé 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. Exécutez 'grunt –Release' pour choisir le fichier index-production.html et quittez le drapeau pour avoir la version de développement.

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

Cette tâche grunt nommée scriptlinker ressemble à un moyen simple d'ajouter les scripts en mode dev. Vous pourriez probablement exécuter une tâche concat en premier et ensuite le signaler à votre fichier concatené en mode prod.

Grunt-dom-munger lit et manipule HTML avec les sélecteurs CSS. Ex. Lisez les tags de votre html. Supprimez les noeuds, ajoutez des nœuds et plus encore.

Vous pouvez utiliser grunt-dom-munger pour lire tous vos fichiers JS qui sont liés par votre index.html, les ugliser et ensuite utiliser grunt-dom-munger à nouveau pour modifier votre index.html pour ne relier que le JS minifié

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 grunt (bien que cela vous limite à dev, prod et test).

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

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

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

Et il dérangera 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 l'adresse IP appropriée pour me connecter à mon API backend. Les blocs changent

  /* 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> 

Grunt-bake est un scénario génial fantastique qui serait génial 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 regarde tous les fichiers dans base.html et les suce pour créer index.html fonctionne fantastique pour les applications multipage (phonegap). Cela permet un développement plus facile car tous les développeurs ne fonctionnent pas sur une seule application longue page (empêchant beaucoup de checkins de conflits). Au lieu de cela, vous pouvez diviser les pages et travailler sur des petits morceaux de code et compiler sur la page complète à l'aide d'une commande de montre.

Bake lit le modèle à partir de base.html et injecte les pages html du composant en attente.

 <!DOCTYPE html> 

Démo mobile de jQuery

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 contextuels», etc. afin que vous puissiez vraiment casser les pages en petits composants gérables.

Utilisez une combinaison de wiredep https://github.com/taptapship/wiredep et usemin https://github.com/yeoman/grunt-usemin afin d'avoir grunt s'occuper de ces tâches. Wiredep ajoutera vos dépendances un fichier de script à la fois, et usemin les concaténera tous dans un seul fichier pour la production. Cela peut être accompli avec juste quelques commentaires html. Par exemple, mes paquets Bower sont automatiquement inclus et ajoutés au html quand je cours bower install && grunt bowerInstall :

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

Considérons processhtml . Il permet la définition de «cibles» multiples pour les compilations. Les commentaires sont utilisés pour inclure ou exclure conditionnellement le matériel du HTML:

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

devient

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

Il prétend même faire des choses astucieuses comme ça (voir LE LECTEUR ):

 <!-- build:[class]:dist production --> <html class="debug_mode"> <!-- /build --> <!-- class is changed to 'production' only when the 'dist' build is executed --> <html class="production"> 

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

Utilisez les modèles Jade … passer des variables à un modèle Jade est un cas d'utilisation standard

J'utilise grunt (grunt-contrib-jade) mais vous ne devez pas utiliser grunt. Utilisez simplement le module standard npm jade.

Si vous utilisez grunt, votre gruntfile voudrait 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 configuré une classe CSS sur la balise HTML en fonction de la valeur de la variable passée et je peux utiliser la logique JavaScript à partir de là, selon que la classe CSS est présente ou non.

C'est génial si vous utilisez Angular puisque vous pouvez faire ng-if's pour inclure des éléments dans la page en fonction de la classe présente.

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

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

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