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

IT技术 javascript build build-automation gruntjs
2021-02-11 13:17:32

我正在尝试使用 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>

问题是,当我运行运行时如何根据配置使 grunt 生成这些 index.html grunt devgrunt prod

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

各位,你们是怎么做的?

6个回答

我最近发现了这些与 Gruntv0.4.0兼容的任务:

  • 咕噜预处理

    围绕预处理 npm module的 Grunt 任务。

  • grunt-env

    Grunt 任务,用于为未来的任务自动化环境配置。

以下是我的Gruntfile.js.

环境设置:

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 上创建了这个演示存储库,它使用我上面概述的技术显示了一个完整的(和更复杂的设置)。

@sthomps 很高兴它有所帮助!自从我发现这些任务以来,我就一直喜欢这个工作流程。仅供参考,我对这个过程做了一个轻微的改变......我没有将几个上下文变量传递给我的 html 模板,而是选择传递一个path : '/<%= pkg.name %>/dist/<%= pkg.version %>/<%= now %>/<%= ver %>'连接所有变量的 var(这是我的构建路径)。在我的模板,我将有:<script src="http://cdn.foo.com<!-- @echo path -->/js/bulldog.min.js"></script>无论如何,我很高兴能够为您节省一些时间!:D
2021-03-15 13:17:32
您的解决方案为我节省了数小时将头撞在墙上的时间。谢谢。
2021-03-22 13:17:32
谢谢,我去看看!
2021-03-28 13:17:32
伙计,我喜欢这个解决方案。它干净、易读且没有过度设计。
2021-03-28 13:17:32
你可以只使用grunt-template做同样的事情,只需data为 dev/prod传入一个不同的对象。
2021-04-04 13:17:32

我想出了我自己的解决方案。还没有完善,但我想我会朝着那个方向前进。

本质上讲,我使用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>

更新。发现基于 grunt 的Yeoman内置了usemin任务,与 Yeoman 的构建系统集成。它根据 index.html 开发版本中的信息以及其他环境设置生成 index.html 的生产版本。有点复杂,但看起来很有趣。

grunt-template是一个非常轻量级的包装器grunt.template.process()(这就是你在这里使用的),这将使这更容易。你可以使用grunt-template做同样的事情,只需data为 dev/prod传入一个不同的对象。
2021-03-22 13:17:32

我不喜欢这里的解决方案(包括我之前给出的解决方案),原因如下:

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

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

为了总结它的工作原理,我有一个带有脚本标签变量的 html 模板。我使用https://github.com/alanshaw/grunt-include-replace来填充该变量。在开发模式下,该变量来自我所有 JS 文件的通配模式。当添加或删除 JS 文件时,watch 任务会重新计算此值。

现在,要在 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是典型的 grunt 文件通配模式。 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 应用程序中做的事情,这是大量的代码。我希望有人可以把它变成一个可重用的插件。也许有一天我会。

@Imray 没有离开我的头顶。你的意思是没有任何形式的模板(例如,grunt-include-replace)?出现在我脑海中的第一个想法是 xslt。不过,可能不是一个好的解决方案。
2021-03-18 13:17:32
这个答案是当场上,虽然我本人除去destPathjsScriptTags和换grunt.file.expandMappinggrunt.file.expand,因为我想要的文件已经在正确的地方。这使事情简化了很多。谢谢@DanielKaplan,你为我节省了大量时间:)
2021-03-18 13:17:32
听起来很有希望。你有机会分享一些片段吗?
2021-03-21 13:17:32
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 你是怎么做到的?
2021-03-22 13:17:32
另一个问题:你知道一种只删除 HTML<script>标签块的方法吗?
2021-04-07 13:17:32

我一直在问自己同样的问题,我认为这个 grunt 插件可以配置为做你想做的事:https : //npmjs.org/package/grunt-targethtml它实现了条件 html 标签,这取决于 grunt 目标。

我见过这个插件,但我不喜欢在我的 index.html 中手动指定所有文件(实际上有任何逻辑)的想法,因为我的 grunt 配置中已经有一个源 js/css 文件列表,但我不喜欢不想重复我自己。底线是 -它不在 index.html 中,您应该决定要包含哪些文件
2021-03-26 13:17:32
+1 为 grunt-targethtml。尽管在 index.html 中添加 if 语句来“决定”加载哪些资产有点难看。不过,还是有道理的。这是您通常希望在项目中包含资源的地方。此外,跟进此事让我检查了 grunt-contrib。它里面有一些很棒的东西。
2021-04-09 13:17:32

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

如何在 gruntfile.js 中放置 if else 块

并提出了以下简单的步骤:

  1. 保留您列出的两个版本的索引文件,并将它们命名为 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 文件并去掉标志以获得开发版本。

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

这里唯一的缺点是需要维护两个 index.html 文件。
2021-03-27 13:17:32