让Grunt为不同的设置生成index.html


208

我正在尝试将Grunt用作我的Web应用程序的构建工具。

我希望至少有两个设置:

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>

二。生产设置 -将我的脚本缩小并合并到一个文件中,

与index.html相应地:

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

问题是,当我运行如何根据配置使它们咕configuration作响?grunt devgrunt prod

还是我朝错误的方向挖掘,总是生成会更容易,MyApp-all.min.js但是将所有脚本(连接的)或从单独文件异步加载这些脚本的加载器脚本放到其中?

你们如何做到?


3
尝试Yeoman工具,其中包括一个“ usemin”任务,可以完成您的任务。此外,Yeamon生成器还包含许多易于学习的“良好实践”,而在使用新工具时很难学习。
EricSonaron 2014年

Answers:


161

我最近发现了这些v0.4.0兼容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月更新:

受到否决的激励...

当我发布此答案时,Grunt没有太多选择0.4.x可以提供适合我需要的解决方案。现在,几个月后,我想那里还有更多的选择可能比我在这里发布的更好。虽然我仍然亲自使用并乐于使用此技术进行构建,但我要求未来的读者花些时间阅读给出的其他答案并研究所有选项。如果您找到更好的解决方案,请在此处发布您的答案。

2014年2月更新:

我不确定这是否会对任何人有帮助,但是我已经在GitHub上创建了这个演示存储库,该存储库使用上面概述的技术显示了完整的(并且更加复杂的设置)。


谢谢,我会检查一下!
Dmitry Pashkevich 2013年

3
您的解决方案使我节省了数小时将头撞在墙上的麻烦。谢谢。
sthomps

1
@sthomps很高兴它有所帮助!自从发现这些任务以来,我一直很喜欢工作流程。仅供参考,我对该过程做了一些细微的更改……我没有传递几个上下文变量给我的html模板,而是选择传递一个path : '/<%= pkg.name %>/dist/<%= pkg.version %>/<%= now %>/<%= ver %>'可连接所有var的var(这是我的构建路径)。在我的模板上,我将拥有:<script src="http://cdn.foo.com<!-- @echo path -->/js/bulldog.min.js"></script>。无论如何,我很高兴能够为您节省一些时间!:D
mhulse,

4
您可以只使用grunt-template进行相同的操作,只需data为dev / prod 传入不同的对象。
Mathias Bynens

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。发现Yeoman基于grunt,具有内置的usemin任务,该任务与Yeoman的构建系统集成在一起。它根据index.html的开发版本以及其他环境设置中的信息生成index.html的生产版本。有点复杂,但有趣。


5
grunt-template是一个非常轻量级的包装器grunt.template.process()(这是您在此处使用的包装器),它将使此操作变得更加容易。您可以使用 grunt-template做同样的事情,只需data为dev / prod传入一个不同的对象。
Mathias Bynens

15

我不喜欢这里的解决方案(包括我先前提供的解决方案),原因如下:

  • 这个问题的投票最高的答案是,你必须手动同步脚本标签的列表,当你添加/重命名/删除JS文件。
  • 可接受答案的问题在于您的JS文件列表不能具有模式匹配。这意味着您必须在Gruntfile中手动对其进行更新。

我已经弄清楚了如何解决这两个问题。我已经设置了grunt任务,以便每次添加或删除文件时,都会自动生成脚本标签来反映这一点。这样,在添加/删除/重命名JS文件时无需修改html文件或grunt文件。

总结一下它是如何工作的,我有一个带有脚本标记变量的html模板。我使用https://github.com/alanshaw/grunt-include-replace填充该变量。在开发模式下,该变量来自我所有JS文件的浮动模式。添加或删除JS文件时,监视任务会重新计算该值。

现在,要在开发或生产模式下获得不同的结果,只需用不同的值填充该变量。这是一些代码:

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是您典型的咕unt文件混淆模式。 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 2015年

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。它实现了依赖于grunt目标的条件html标签。


2
我看过这个插件,但是我不喜欢在index.html中手动指定所有文件(实际上具有任何逻辑)的想法,因为我已经在grunt配置中包含了源js / css文件列表,并且不想重复我自己。底线是- 它不在index.html中,您应该在其中决定要包含的文件
Dmitry Pashkevich 2012年

为grunt-targethtml +1。尽管在index.html中添加if语句以“决定”要加载的资产有点麻烦。不过,这确实是有道理的。这是您通常希望在项目中包含资源的地方。此外,跟进此事还导致我检查了grunt-contrib。它包含一些很棒的东西。
carbontax

8

我一直在寻找一个更简单,直接的解决方案,因此我结合了这个问题的答案:

如何将其他地方放置在gruntfile.js中

并提出以下简单步骤:

  1. 保留列出的两个版本的索引文件,并将其命名为index-development.html和index-prodoction.html。
  2. 在Gruntfile.js的concat / copy块中为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. 运行'grunt --Release'选择index-production.html文件,并保留该标志以获取开发版本。

没有要添加或配置的新插件,也没有新的繁琐任务。


3
唯一的缺点是要维护两个index.html文件。
亚当·马歇尔2014年

5

这个名为scriptlinker的繁琐任务看起来像是在开发人员模式下添加脚本的简便方法。您可能首先运行concat任务,然后将其指向prod模式下的串联文件。


+1。文档令人困惑,某些东西(appRoot,相对)并非总是按预期运行,但仍然是有用的工具。
hashchange

1
@hashchange我不使用此工具。我最终使用github.com/alanshaw/grunt-include-replace代替。我的html文件中有一个表示脚本标签的变量。然后,用所需的html的字符串填充该变量。在开发人员模式下,此变量是脚本列表。在生产模式下,此变量是串联的缩小版本。
丹尼尔·卡普兰

感谢您指向grunt-include-replace的指针。(实际上,我需要一个工具来将目录中的所有规范文件添加到Mocha index.html文件中。Scriptlinker可以满足要求。)
hashchange 2014年

@hashchange您对文档的掌握是正确的。您如何告诉它将脚本图块放在html文件中的何处
丹尼尔·卡普兰

1
您定义一个HTML注释。看一下这个文件。插入发生在<!--SINON COMPONENT SCRIPTS--><!--SPEC SCRIPTS-->。而这里是做它(实际的工作之一,而不是东西的文档)繁重的任务。希望对您
有所

5

grunt-dom-munger使用CSS选择器读取和操作HTML。例如 从html读取标签。删除节点,添加节点等等。

您可以使用grunt-dom-munger读取由index.html链接的所有JS文件,对其进行丑化,然后再次使用grunt-dom-munger修改index.html以仅链接缩小的JS


5

我找到了一个名为grunt-dev-prod-switch的grunt插件。它所做的只是基于传递给grunt的--env选项注释掉它所查找的某些块(尽管它限制了您进行开发,生产和测试)。

按照此处的说明进行设置后,即可运行例如:

grunt serve --env=dev,它所做的就是注释掉由

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

它将取消注释被包装的块

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

它也可以在javascript上使用,我用它来设置正确的IP地址以连接到我的后端API。块变成

    /* 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)非常有用。由于所有开发人员都无法使用一个较长的单页应用程序,因此开发更加轻松(避免了许多冲突签入)。相反,您可以分解页面并处理较小的代码块,并使用监视命令将其编译为整页。

Bake从base.html读取模板,并在watch上注入组件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的代码演示来增强答案?
Dmitry Pashkevich 2013年

4

结合使用wiredep https://github.com/taptapship/wiredep和usemin https://github.com/yeoman/grunt-usemin可以使咕unt 声照顾这些任务。Wiredep将一次添加您的依赖项一个脚本文件,而usemin会将它们全部连接到一个文件中以进行生产。然后,仅需一些html注释即可完成此操作。例如,当我运行时,我的Bower软件包会自动包含并添加到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在页面中包含元素。

例如,如果存在该类,我可能会包含一个脚本...

(例如,我可能将实时重新加载脚本包含在dev中,但不包含在生产环境中)

<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">
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.