208 votes

Faire générer par Grunt un index.html pour différentes configurations

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

Je veux avoir au moins deux configurations :

I. Configuration du développement - charger des 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 la suivante , Comment puis-je faire en sorte que Grunt crée ces index.html en fonction de la configuration lorsque je lance le programme. 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 chargeur script qui charge de manière asynchrone ces scripts à partir de fichiers séparés ?

Comment vous faites, les gars ?

161voto

mhulse Points 1992

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

  • grunt-preprocess

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

  • grunt-env

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

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

Configuration de l'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 %>'
            }

        }

    }

}

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 /src/tmpl/index.html fichier modèle (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épendra de votre situation. En ce qui me concerne, bien que ce soit un bout de code génial, le Yeoman grunt-usemin est plus robuste que ce dont j'ai personnellement besoin.

NOTE : I juste J'ai découvert les tâches énumérées ci-dessus aujourd'hui, il se peut donc qu'il me manque une fonctionnalité et/ou que mon processus change à l'avenir. Pour l'instant, j'aime la simplicité et des caractéristiques qui grunt-preprocess et grunt-env ont à 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 adaptée à mes besoins. Aujourd'hui, des mois plus tard, je pense qu'il y a plus d'options sur le marché que ce que j'avais imaginé. pourrait être meilleur que ce que j'ai posté ici. Bien que j'utilise toujours personnellement, et avec plaisir, cette technique pour mes constructions Je demande aux futurs lecteurs de prendre le temps de lire les autres réponses données 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 suis pas sûr que cela puisse aider qui que ce soit, mais j'ai créé ce dépôt de démonstration sur GitHub qui montre une installation complète (et plus complexe) utilisant la ou les techniques que j'ai décrites ci-dessus.

34voto

Dmitry Pashkevich Points 4160

J'ai trouvé ma propre solution. Elle n'est pas encore au point, 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 produit soit une liste de mes fichiers sources d'origine, soit des liens vers un seul fichier avec le code minifié. L'exemple ci-dessous concerne les fichiers js mais la même approche peut être étendue aux fichiers 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. J'ai découvert que Yeoman qui est basé sur grunt, a une fonctionnalité intégrée de usemin qui s'intègre au système de construction de Yeoman. Elle génère une version de production de l'index.html à partir des informations contenues dans la version de développement de l'index.html ainsi que d'autres paramètres d'environnement. Un peu sophistiqué mais intéressant à regarder.

15voto

tieTYT Points 15326

Je n'aime pas les solutions proposées 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 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 motifs. Cela signifie que vous devez la mettre à jour à la main dans le fichier Grunt.

J'ai trouvé 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 script sont automatiquement générées 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 script. J'utilise https://github.com/alanshaw/grunt-include-replace pour remplir cette variable. En mode dev, cette variable provient d'un modèle de globbing de tous mes fichiers JS. La tâche de veille recalcule cette valeur lorsqu'un fichier JS est ajouté ou supprimé.

Maintenant, pour obtenir des résultats différents en mode dev ou prod, il suffit de remplir cette variable avec une valeur différente. Voici un peu de 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 grunt file-globbing. jsScriptTags prend le jsSrcFileArray et les concatène avec script des étiquettes des deux côtés. destPath est le préfixe que je veux sur chaque fichier.

Et voici à quoi ressemble 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 sous forme d'une valeur codée en dur. script lorsqu'il est exécuté dans prod mode. En mode dev, cette variable se développera en 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.

13voto

Je me suis posé 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 de grunt.

8voto

guofoo Points 83

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

Comment placer le bloc if else dans le fichier gruntfile.js ?

et j'ai trouvé 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. Exécutez 'grunt --Release' pour choisir le fichier index-production.html et laissez le drapeau pour avoir la version de développement.

Aucun nouveau plugin à ajouter ou à configurer et aucune nouvelle tâche grunt.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X