さまざまな設定でGruntにindex.htmlを生成させる


208

私は私のウェブアプリのビルドツールとしてGruntを使用しようとしています。

少なくとも2つの設定が必要です。

I.開発設定 -連結せずに、個別のファイルからスクリプトをロードします。

したがって、私のindex.htmlは次のようになります。

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

II。本番環境のセットアップ -スクリプトを縮小して1つのファイルに連結し、

index.htmlに応じて:

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

問題は、実行時に設定に応じてこれらのindex.htmlをどうすればgrunt devよいのgrunt prodでしょうか。

あるいは、間違った方向に掘っていて、常に生成する方が簡単ですMyApp-all.min.jsが、すべてのスクリプト(連結)または別のファイルからこれらのスクリプトを非同期で読み込むローダースクリプトをその中に配置する方が簡単でしょうか。

みんなどうするの?


3
Yeomanツールを試してみてください。これには、自分のタスクを実行する「usemin」タスクが含まれています。さらに、Yeamonジェネレーターには、新しいツールを使用するときに習得するのが難しい、簡単に習得できる「グッドプラクティス」が多数含まれています。
EricSonaron、2014

回答:


161

私は最近これらのGrunt v0.4.0互換タスクを発見しました:

  • うなり声前処理

    プリプロセスnpmモジュールの周りのグラントタスク。

  • grunt-env

    将来のタスクのために環境設定を自動化するGruntタスク。

以下は、私のスニペットGruntfile.jsです。

ENVセットアップ:

env : {

    options : {

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

    },

    dev: {

        NODE_ENV : 'DEVELOPMENT'

    },

    prod : {

        NODE_ENV : 'PRODUCTION'

    }

},

前処理:

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

        }

    }

}

タスク:

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

そして/src/tmpl/index.htmlテンプレートファイル(例えば)では:

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

私の設定はほとんどの人とは異なると思います。上記の有用性は状況によって異なります。私にとっては、コードの素晴らしい部分ですが、Yeoman grunt-useminは個人的に必要なものよりも堅牢です。

注:私はちょうど私が機能を失われる可能性があるので、今日は上記のタスクを発見し、および/または、私のプロセスが道を変更することがあります。今のところ、grunt-preprocessgrunt-envが提供する必要があるシンプルさ機能が大好きです。:)


2014年1月の更新:

反対票による動機...

私がこの回答を投稿したとき0.4.x、私のニーズに合ったソリューションを提供するGruntのオプションはあまりありませんでした。さて、ヶ月後、私はそこに多くのオプションがあることを推測することができ、私はここに掲載したものよりも優れているが。私は今でも自分のビルドでこのテクニックを個人的に使用して楽しんでいますが、将来の読者には、与えられた他の回答を読んですべてのオプションを調査するように時間をかけてください。より良い解決策を見つけたら、ここに回答を投稿してください。

2014年2月の更新:

誰かに役立つかどうかはわかりませんが、GitHubこのデモリポジトリを作成しました。このデモリポジトリは、上記で概説した手法を使用して完全な(そしてより複雑なセットアップを)示しいます。


よろしくお願いします!
ドミトリーパシュケビッチ

3
あなたの解決策は、頭を壁にぶつける時間を数時間節約しました。ありがとう。
sthomps 2013年

1
@sthompsよかった!これらのタスクを発見して以来、私はワークフローに夢中になっています。ちなみに、プロセスにわずかな変更を加えました... HTMLテンプレートにいくつかのコンテキスト変数を渡す代わりに、path : '/<%= pkg.name %>/dist/<%= pkg.version %>/<%= now %>/<%= ver %>'すべての変数を連結する1つの変数を渡すことを選択しました(それが私のビルドパスです)。私のテンプレートには次のものがあります<script src="http://cdn.foo.com<!-- @echo path -->/js/bulldog.min.js"></script>。とにかく、時間を節約できて嬉しいです!:D
mhulse

4
dev / prodに別のオブジェクトを渡すだけで、grunt-templateのみを使用して同じことを行うことができますdata
Mathias Bynens 2013年

2
男私はこのソリューションが大好きです。クリーンで読みやすく、過度に設計されていません。
Gaurang Patel

34

私は自分の解決策を思いついた。まだ洗練されていませんが、その方向に進んでいくと思います。

本質的に、私はgrunt.template.process()を使用してindex.html、現在の構成を分析し、元のソースファイルのリストまたは縮小されたコードを含む単一のファイルへのリンクを生成するテンプレートから自分を生成しています。以下の例はjsファイルの場合ですが、同じアプローチをcssおよびその他の可能なテキストファイルに拡張できます。

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

最後にindex.tmpl、生成ロジックが組み込まれています。

<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。ことが判明ヨーマンうなり声に基づいており、内蔵されていuseminのタスクヨーマンのビルドシステムと統合することを。index.htmlの開発バージョンの情報とその他の環境設定から、index.htmlの製品バージョンを生成します。少し洗練されていますが、注目に値します。


5
grunt-templateは、grunt.template.process()これをさらに簡単にする非常に軽量なラッパー(ここで使用しているもの)です。dev / prodに別のオブジェクトを渡すだけで、 grunt-templateを使用して同じことを行うことができますdata
Mathias Bynens 2013年

15

私はここでの解決策(以前に私が与えたものを含む)が嫌いであり、ここに理由があります:

  • 投票数が最も多い回答の問題は JSファイルを追加/名前変更/削除するときにスクリプトタグのリストを手動で同期する必要があることです。
  • 受け入れられた回答の問題は JSファイルのリストにパターンマッチングを設定できないことです。つまり、Gruntfileで手動で更新する必要があります。

私はこれらの問題の両方を解決する方法を見つけました。ファイルが追加または削除されるたびにスクリプトタグが自動的に生成され、それを反映するように、うなり声タスクを設定しました。このようにして、JSファイルを追加/削除/名前変更するときに、htmlファイルやgruntファイルを変更する必要はありません

それがどのように機能するかを要約すると、スクリプトタグの変数を含むhtmlテンプレートがあります。私はhttps://github.com/alanshaw/grunt-include-replaceを使用してその変数に値を入力しています。開発モードでは、その変数はすべてのJSファイルのグロビングパターンから取得されます。JSファイルが追加または削除されると、監視タスクはこの値を再計算します。

これで、devモードまたはprodモードで異なる結果を取得するには、その変数に異なる値を入力するだけです。ここにいくつかのコードがあります:

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は、典型的な不快なファイル展開パターンです。 jsScriptTagsを取り、両側のタグとjsSrcFileArray一緒にそれらを連結しscriptます。 destPath各ファイルに付ける接頭辞です。

そして、HTMLは次のようになります。

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

</head>

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

これで、設定で確認できるように、変数をモードでscript実行すると、その値がハードコードされたタグとして生成されprodます。開発モードでは、この変数は次のような値に展開されます。

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

ご不明な点がありましたらお知らせください。

PS:これは、すべてのクライアント側JSアプリで私がやりたいことを実行するために必要なコードの量が非常に多いものです。誰かがこれを再利用可能なプラグインに変えられることを願っています。多分私はいつか行きます。


1
有望に聞こえます。いくつかのスニペットを共有できますか?
アダムマーシャル

I've set up my grunt task so that every time a file is added or deleted, the script tags automatically get generated to reflect thatどうやったの?
CodyBugstein

2
別の質問:HTML <script>タグのブロックを削除する方法を知っていますか?
CodyBugstein、2015年

@Imrayは頭のてっぺんから離れていません。テンプレートの形式がないことを意味しますか(たとえば、grunt-include-replace)?私の頭に浮かぶ最初の考えはxsltでしょう。しかし、おそらく良い解決策ではありません。
Daniel Kaplan 2015年

1
この答えは正解ですが、必要なファイルが既に正しい場所にあったため、個人的に削除destPathjsScriptTagsて交換grunt.file.expandMappinggrunt.file.expandました。これは物事を大幅に簡略化しました。@DanielKaplanに感謝します。時間を大幅に節約できました:)
DanielM 2015年

13

私はしばらく同じ質問をしてきましたが、このgruntプラグインはあなたが望むことを実行するように構成できると思います:https : //npmjs.org/package/grunt-targethtml。それは、不快なターゲットに依存する条件付きのhtmlタグを実装します。


2
私はこのプラグインを見てきましたが、私のgrunt設定に既にソースjs / cssファイルのリストがあり、繰り返したい。ボトムラインはある- それはあなたが含まれるファイルを決定する必要がありindex.htmlの中ではありません
ドミトリーPashkevich

grunt-targethtmlの+1。ロードするアセットをindex.htmlで「決定」するifステートメントを追加するのは少し醜いですが。それでも、それは理にかなっています。これは、プロジェクトにリソースを含めるために通常見る場所です。また、これに続いて、私はうなり声貢献をチェックするようになりました。それはいくつかの素晴らしいものを持っています。
炭素税2012年

8

私はもっ​​とシンプルで簡単な解決策を探していたので、この質問の答えを組み合わせました:

他にブロックする場合の配置方法gruntfile.js

そして、次の簡単な手順を思い付きました:

  1. リストしたとおりに2つのバージョンのインデックスファイルを保持し、それらにindex-development.htmlおよびindex-prodoction.htmlという名前を付けます。
  2. index.htmlファイルのGruntfile.jsのconcat / copyブロックで次のロジックを使用します。

    concat: {
        index: {
            src : [ (function() {
                if (grunt.option('Release')) {
                  return 'views/index-production.html';
                } else {
                  return 'views/index-development.html';
                }
              }()) ],
           dest: '<%= distdir %>/index.html',
           ...
        },
        ...
    },
  3. 'grunt --Release'を実行してindex-production.htmlファイルを選択し、フラグをオフにして開発バージョンにします。

追加または設定する新しいプラグインはなく、新しい不快なタスクもありません。


3
ここでの唯一の欠点は、維持する2つのindex.htmlファイルがあることです。
アダムマーシャル

5

scriptlinkerという名前の面倒なタスクは、開発モードでスクリプトを追加する簡単な方法のように見えます。おそらく最初にconcatタスクを実行してから、それをprodモードで連結ファイルを指すようにすることができます。


+1。ドキュメントは紛らわしく、一部のもの(appRoot、relative)は常に意図したとおりに機能するとは限りませんが、それでも役立つツールです。
hashchange、2014年

1
@hashchangeこのツールは使用しません。代わりにgithub.com/alanshaw/grunt-include-replaceを使用してしまいました。HTMLファイルにスクリプトタグを表す変数があります。次に、その変数に必要なHTMLの文字列を入力します。開発モードでは、この変数はスクリプトのリストです。prodモードでは、この変数は連結された縮小バージョンです。
Daniel Kaplan 2014年

grunt-include-replaceへのポインタをありがとう。(実際には、ディレクトリ内のすべてのスペックファイルをMocha index.htmlファイルに追加するためのツールが必要でした。Scriptlinkerはそのために
問題ありません

@hashchangeは、ドキュメントの吸引について正しい。スクリプトタイルをhtmlファイルのどこに配置するかをどのように伝えますか?
Daniel Kaplan

1
HTMLコメントを定義します。このファイルを見てください。挿入はで起こる<!--SINON COMPONENT SCRIPTS--><!--SPEC SCRIPTS-->。そして、ここで(ドキュメント中のものとは反対に、実際の作業1)それをしないうなり声タスクがあります。それがお役に立て
ば幸い

5

grunt-dom-mungerは、CSSセレクターを使用してHTMLを読み取り、操作します。例 あなたのhtmlからタグを読んでください。ノードの削除、ノードの追加など。

grunt-dom-mungerを使用して、index.htmlによってリンクされているすべてのJSファイルを読み取り、それらを醜くし、再度grunt-dom-mungerを使用して、縮小されたJSのみをリンクするようにindex.htmlを変更できます。


5

grunt-dev-prod-switchというイヤなプラグインを見つけました。実行するのは、gruntに渡す--envオプションに基づいて検索する特定のブロックをコメント化することだけです(ただし、dev、prod、およびtestに制限されます)。

ここで説明するように設定したら、たとえば次のように実行できます。

grunt serve --env=dev、そしてそれが行うすべてはによってラップされているブロックをコメントアウトすることです

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

そして、それによってラップされているブロックのコメントを外します

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

また、JavaScriptでも機能します。これを使用して、バックエンドAPIに接続するための正しいIPアドレスを設定します。ブロックは次のように変わります

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

あなたの場合、それは次のように簡単です:

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

4

grunt-bakeは、ここでうまく機能する素晴らしいgruntスクリプトです。JQM自動ビルドスクリプトで使用します。

https://github.com/imaginethepoet/autojqmphonegap

私のgrunt.coffeeファイルを見てください:

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

これは、base.html内のすべてのファイルを調べ、それらを吸い込んでindex.htmlを作成します。これは、マルチページアプリ(phonegap)に最適です。すべての開発者が1つの長い単一ページのアプリで作業していないため、これにより開発が容易になります(多くの競合チェックインが防止されます)。代わりに、ページを分割してコードの小さなチャンクで作業し、watchコマンドを使用してページ全体にコンパイルできます。

Bakeはbase.htmlからテンプレートを読み取り、コンポーネントのHTMLページをウォッチに挿入します。

<!DOCTYPE html>

jQuery Mobileデモ

app.initialize();

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

これをさらに一歩進めて、ページに「メニュー」「ポップアップ」などのインジェクションを追加できるので、ページをより小さな管理しやすいコンポーネントに分割できます。


たぶん、grunt-bakeを使用するコードデモで答えを拡張できますか?
ドミトリーパシュケビッチ

4

これらのタスクをgruntで処理するには、wiredep https://github.com/taptapship/wiredepとusemin https://github.com/yeoman/grunt-useminを組み合わせて使用​​します。Wiredepは依存関係を一度に1つのスクリプトファイルに追加し、useminはそれらをすべて1つのファイルに連結して本番用に作成します。これは、いくつかのhtmlコメントで実現できます。たとえば、私が実行すると、私のバウアーパッケージが自動的に含まれ、htmlに追加されますbower install && grunt bowerInstall

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

2

この答えは初心者向けではありません!

Jadeテンプレートを使用する... Jadeテンプレートに変数を渡すことは、沼地の標準的な使用例です

私はgrunt(grunt-contrib-jade)を使用していますが、gruntを使用する必要はありません。標準のnpm jadeモジュールを使用するだけです。

gruntを使用する場合、gruntfileは次のようになります...

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

Jadeテンプレートでgruntから渡されたデータに簡単にアクセスできるようになりました。

Modernizrで使用されるアプローチと同様に、渡された変数の値に従ってHTMLタグにCSSクラスを設定し、CSSクラスが存在するかどうかに基づいて、そこからJavaScriptロジックを使用できます。

これは、Angularを使用する場合に最適です。ng-ifを使用して、クラスが存在するかどうかに基づいてページに要素を含めることができるためです。

たとえば、クラスが存在する場合はスクリプトを含めることができます...

(たとえば、私はライブリロードスクリプトを開発に含めても、本番環境には含めない場合があります)

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

2

processhtmlを検討してください。ビルドの複数の「ターゲット」を定義できます。コメントは、条件に応じてHTMLにマテリアルを含めたり除外したりするために使用されます。

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

なる

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

それはこのような気の利いたことをすることさえ主張している(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">
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.