我正在尝试使用 Grunt 作为我的 webapp 的构建工具。
我想至少有两个设置:
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 dev
或 grunt prod
时,如何根据配置使 grunt 生成这些 index.html?
或者,也许我在挖掘错误的方向,总是生成 MyApp-all.min.js
会更容易,但将我的所有脚本(连接)或从单独文件异步加载这些脚本的加载器脚本放入其中?
你们是怎么做到的,伙计们?
我最近发现了这些与 Grunt v0.4.0
兼容的任务:
grunt-preprocess 围绕预处理 npm 模块的 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-preprocess 和 grunt-env 提供的简单和功能。 :)
2014 年 1 月更新:
受到反对票的推动...
当我发布此答案时,Grunt 0.4.x
没有多少选项可以提供满足我需求的解决方案。现在,几个月后,我想有更多的选择,可能比我在这里发布的更好。 虽然我个人仍然在我的构建中使用并喜欢使用这种技术,但我要求未来的读者花时间阅读给出的其他答案并研究所有选项。 如果您找到更好的解决方案,请在此处发布您的答案。
2014 年 2 月更新:
我不确定它是否对任何人有任何帮助,但我创建了 this demo repository on GitHub,它使用我上面概述的技术显示了一个完整的(和更复杂的设置)。
我想出了我自己的解决方案。还没有完善,但我想我会朝那个方向前进。
从本质上讲,我正在使用 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。 发现基于 grunt 的 Yeoman 具有与 Yeoman 的构建系统集成的内置 usemin 任务。它根据 index.html 开发版本中的信息以及其他环境设置生成 index.html 的生产版本。有点复杂但看起来很有趣。
grunt.template.process()
的一个非常轻量级的包装器(这就是您在此处使用的),这将使这变得更加容易。您可以使用 grunt-template 执行相同的操作,只需为 dev/prod 传入不同的 data
对象。
我不喜欢这里的解决方案(包括 the one I previously gave),原因如下:
投票率最高的问题是,您必须在添加/重命名/删除 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>
现在,正如您在配置中看到的那样,当它在 prod
模式下运行时,我将该变量的值生成为硬编码的 script
标记。在开发模式下,此变量将扩展为如下值:
<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 应用程序中做的事情,这是一个疯狂的代码量。我希望有人可以把它变成一个可重用的插件。也许有一天我会。
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
你是怎么做到的?
<script>
标记块的方法吗?
jsScriptTags
中删除了 destPath
并将 grunt.file.expandMapping
与 grunt.file.expand
交换,因为我想要的文件已经在正确的位置。这简化了很多事情。谢谢@DanielKaplan,你为我节省了大量时间:)
一段时间以来,我一直在问自己同样的问题,我认为这个 grunt 插件可以配置为执行您想要的操作:https://npmjs.org/package/grunt-targethtml。它实现了依赖于 grunt 目标的条件 html 标签。
我一直在寻找一个更简单、直接的解决方案,所以我结合了这个问题的答案:
How to place if else block in gruntfile.js
并想出了以下简单的步骤:
保留列出的索引文件的两个版本,并将它们命名为 index-development.html 和 index-prodoction.html。在 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', ... }, .. . },运行 'grunt --Release' 选择 index-production.html 文件并去掉标志以获得开发版本。
没有要添加或配置的新插件,也没有新的 grunt 任务。
这个名为 scriptlinker 的 grunt 任务看起来像是在开发模式下添加脚本的一种简单方法。您可以先运行一个 concat 任务,然后在 prod 模式下将其指向您的连接文件。
grunt-dom-munger 使用 CSS 选择器读取和操作 HTML。前任。从您的 html 中读取标签。删除节点、添加节点等。
您可以使用 grunt-dom-munger 读取由 index.html 链接的所有 JS 文件,将它们丑化,然后再次使用 grunt-dom-munger 修改 index.html 以仅链接缩小的 JS
我找到了一个叫 grunt-dev-prod-switch 的 grunt 插件。它所做的只是根据您传递给 grunt 的 --env 选项注释掉它寻找的某些块(尽管它限制您只能进行开发、生产和测试)。
按照说明 here 进行设置后,您可以运行以下示例:
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>
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) 非常有效。这使得开发更容易,因为所有开发人员都没有在一个长的单页应用程序上工作(防止大量冲突签入)。相反,您可以拆分页面并处理较小的代码块,然后使用 watch 命令编译到整个页面。
Bake 从 base.html 读取模板并在 watch 上注入组件 html 页面。
<!DOCTYPE html>
jQuery 移动演示
app.initialize();
<body>
<!--(bake /resources/custom/components/page1.html)-->
<!--(bake /resources/custom/components/page2.html)-->
<!--(bake /resources/custom/components/page3.html)-->
</body>
您可以更进一步,在您的页面中为“菜单”“弹出窗口”等添加注入,这样您就可以真正将页面分解为更小的可管理组件。
使用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 -->
这个答案不适合菜鸟!
使用 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>
考虑 processhtml。它允许为构建定义多个“目标”。注释用于有条件地在 HTML 中包含或排除材料:
<!-- build:js:production js/app.js -->
...
<!-- /build -->
变成
<script src="js/app.js"></script>
它甚至声称可以做这样的漂亮事情(参见 the 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">
不定期副业成功案例分享
path : '/<%= pkg.name %>/dist/<%= pkg.version %>/<%= now %>/<%= ver %>'
来连接所有变量(这是我的构建路径)。在我的模板上,我将拥有:<script src="http://cdn.foo.com<!-- @echo path -->/js/bulldog.min.js"></script>
。无论如何,我很高兴能够为您节省一些时间! :Ddata
对象。