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

我试图使用Grunt作为我的web应用程序的构build工具。

我想至less有两个设置:

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。 生产设置 – 加载我的脚本缩小和连接在一个文件中,

与index.html相应:

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

问题是, 当我运行grunt devgrunt prod怎样才能让这些index.html取决于configuration呢?

或者,也许我正在朝错误的方向挖掘,总是会生成MyApp-all.min.js但将所有脚本(连接在一起)或从同一个文件中asynchronous加载这些脚本的加载器脚本放在里面?

你怎么做,伙计?

我最近发现了这些Grunt v0.4.0兼容任务:

  • 咕噜,预处理

    围绕预处理npm模块的Grunt任务。

  • 咕噜-ENV

    Grunt任务为将来的任务自动化环境configuration。

下面是我的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比我个人需要的更强大。

注:刚刚发现了上面列出的任务,所以我可能会错过一个function和/或我的过程可能会改变。 现在,我喜欢grunt-preprocess和grunt-env所提供的简单性function。 🙂


2014年1月更新:

受到投票的激励

当我发布这个答案时,Grunt 0.4.x提供了一个解决scheme,可以满足我的需求。 现在,几个月后,我猜想有更多的select可以比我在这里发布的更好。 虽然我仍然亲自使用,并喜欢使用这种技术为我的构build ,我要求未来的读者花时间阅读给出的其他答案,并研究所有的选项。 如果你find更好的解决scheme,请在这里发表你的答案。

2014年2月更新:

我不确定它是否对任何人有任何帮助,但是我已经在GitHub上创build了这个演示版本库,它显示了一个完整的(和更复杂的设置)使用我上面概述的技术。

我已经提出了自己的解决scheme。 还没有打磨,但我想我会朝这个方向前进。

在本质上,我使用grunt.template.process()从模板生成我的index.html ,该模板分析当前configuration,并生成我原始源文件的列表或者链接到带有缩小代码的单个文件。 下面的例子是为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。 发现基于grunt的Yeoman有一个内置的usemin任务,与Yeoman的构build系统集成。 它从index.html开发版本中的信息以及其他环境设置生成index.html的生产版本。 有点复杂但有趣的看看。

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

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

我已经想出了如何解决这两个问题。 我已经设置了我的咕task任务,以便每次添加或删除文件时,脚本标记自动生成以反映。 这样,当你添加/删除/重命名你的JS文件时, 你不需要修改你的html文件或grunt文件。

为了总结这是如何工作的,我有一个html模板,其中包含一个用于脚本标记的variables。 我使用https://github.com/alanshaw/grunt-include-replace填充该variables。; 在开发模式下,该variables来自我所有的JS文件的globbing模式。 监视任务在添加或删除JS文件时重新计算此值。

现在,要在dev或prod模式下获得不同的结果,只需使用不同的值填充该variables即可。 这是一些代码:

 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文件jsSrcFileArray模式。 jsScriptTags接受jsSrcFileArray并将它们与script标记连接在一起。 destPath是我想要在每个文件上的前缀。

这里是HTML的样子:

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

现在,正如你在configuration中看到的那样,当它以prod模式运行时,我将该variables的值作为硬编码的script标记生成。 在开发模式下,这个variables会扩展为这样的值:

 <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应用程序中做的事情的一个疯狂的数量。 我希望有人可以把它变成一个可重用的插件。 也许我会有一天。

我一直在问自己同样的问题,我觉得这个咕噜的插件可以configuration成你想要的: https : //npmjs.org/package/grunt-targethtml 。 它实现了条件html标签,这取决于grunt目标。

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

如何在gruntfile.js中放置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”来selectindex-production.html文件,并离开标志来获得开发版本。

没有新的插件添加或configuration,没有新的咕噜任务。

这个名为scriptlinker的咕噜任务看起来像是一种在dev模式下添加脚本的简单方法。 你可以先运行一个concat任务,然后以prod模式指向你的连接文件。

grunt-dom-munger使用CSSselect器读取和操作HTML。 防爆。 从你的html阅读标签。 删除节点,添加节点等等。

你可以使用grunt-dom-munger来读取你的index.html链接的所有JS文件,将它们丑化,然后再使用grunt-dom-munger来修改你的index.html来只链接缩小的JS

我发现了一个名为grunt-dev-prod-switch的grunt插件。 它所做的就是根据你传递给grunt的一个–env选项(尽pipe它限制你的开发,产品和testing)注释掉它所寻找的某些块。

一旦你在这里解释它的设置,你可以运行例如:

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> 

咕噜咕噜是一个梦幻般的咕script脚本,将在这里工作很好。 我在我的JQM自动构build脚本中使用它。

https://github.com/imaginethepoet/autojqmphonegap

看看我的grunt.coffee文件:

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

这看起来base.html中的所有文件,并吸引他们创造index.html作品奇妙的多页应用程序(phonegap)。 由于所有的开发人员都不能在一个长单页面应用程序上工作(这样可以防止大量的冲突检查),因此可以更轻松地开发。 相反,您可以拆分页面,并使用更小的代码块,并使用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> 

你可以更进一步,在你的页面中添加“菜单”,“popup窗口”等的注入,这样你就可以将页面切换成更小的可pipe理组件。

使用wiredep https://github.com/taptapship/wiredep和usemin https://github.com/yeoman/grunt-usemin的组合来使grunt处理这些任务。; Wiredep将一次添加一个脚本文件的依赖关系,usemin将它们连接成一个文件进行生产。 这只需要一些html注释即可完成。 例如,当我运行bower install && grunt bowerInstall时,我的bower软件包会自动包含并添加到html中:

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

考虑processhtml 。 它允许为构build定义多个“目标”。 评论用于有条件地包含或排除HTML中的材料:

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

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

它甚至声称做这样的漂亮的东西(见自述文件 ):

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

这个答案不适用于noobs!

使用Jade模板…将variables传递给Jade模板是一个bog标准用例

我正在使用grunt(grunt-contrib-jade),但是你不必使用grunt。 只需使用标准的npm玉器模块。

如果使用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使用的方法一样,我根据传递的variables的值在HTML标签上设置一个CSS类,并可以根据CSS类是否存在使用JavaScript逻辑。

如果使用Angular,这是非常好的,因为你可以通过ng-if来根据类是否存在将元素包含在页面中。

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

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

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