Grunt-js-秘籍-全-
Grunt.js 秘籍(全)
原文:
zh.annas-archive.org/md5/7115bca88efa642c1e86ac41014aec5d译者:飞龙
前言
Grunt 是一个任务自动化工具,它为不断增长的插件生态系统提供了平台。这些插件提供了可以自动化的任务的实际实现。文件管理、测试、部署、模板渲染、代码生成以及更多都可以通过易于配置的任务自动化。
Grunt 平台的关键优势在于它定义的简单任务配置方法,以及它所支持的插件都倾向于遵循这种方法。这为开发者提供了一种更快的方式去使用那些原本需要更多时间学习的工具,同时也以标准格式呈现配置,便于理解、操作和共享。
《Grunt 食谱》旨在提供易于遵循的说明,以自动化项目需要定期执行的所有各种任务。我们将从最基本的常见任务开始,逐步过渡到创建自己的任务,最终仅使用 Grunt 生成整个网站。
本书各章节将分别关注一个特定主题,并为每个主题提供一系列食谱。许多食谱将探索使用不同工具或库自动化类似任务,每个食谱也将探索其主题的流行变体,以防你有更具体的需求。
本书涵盖内容
第一章, 开始使用 Grunt,从设置 Grunt 以在基于 Node.js 的项目中使用开始,并涵盖了某些更常见的自动化任务。
第二章, 文件管理,涵盖了文件的复制、压缩、链接、连接和下载。在软件项目中处理文件可能是我们最常遇到的任务之一。
第三章, 模板引擎,涵盖了使用一些更受欢迎的引擎进行模板的渲染、编译和打包。从模板中渲染内容在基于 Web 的项目开发中是至关重要的。
第四章, 生成 CSS 和 JavaScript,涵盖了 CSS 和 JavaScript 代码的生成。使用编译为 JavaScript 的新语言和使用生成 CSS 的 CSS 预处理器可以节省时间并提高项目的灵活性。
第五章, 运行自动化测试,涵盖了测试套件的运行和生成代码覆盖率报告。自动化测试已成为所有大型软件项目的必要部分,并且是确保代码稳定性和质量的无价工具。
第六章, 部署准备,涵盖了图像优化、CSS 压缩、确保 JavaScript 代码的质量、压缩它,并将所有这些打包成一个源文件。
第七章, 部署到最终用户,涵盖了将文件传输到网络位置、刷新缓存服务以及在远程服务器上运行命令。一旦我们有一个功能齐全且优化的 Web 应用程序,就是时候让它对目标用户可访问了。
第八章, 创建自定义任务,涵盖了创建我们自己的自定义任务的所有方面。在某个时候,你可能会遇到一些你想自动化但找不到完全符合你需求的 Grunt 任务的情况。这时,构建一个自定义任务可以对你的操作变得非常有价值,甚至可能让你成为英雄。
第九章, 编写插件,涵盖了发现插件、为现有插件项目做出贡献以及创建我们自己的插件项目的过程。没有插件生态系统的 Grunt 项目将一无是处,作为开发者,我们可以参与它们的创建和演变。
第十章, 静态网站,使用 Assemble 插件生成整个静态网站。对于托管内容变化不大的简单网站,可以一次性生成并上传到托管服务。
本书所需内容
使用本书中的食谱的唯一要求是安装 Node.js。这可以通过下载并运行安装程序、从源代码编译、使用 Node 版本管理器(github.com/creationix/nvm)这样的工具,或者使用操作系统支持的包管理器来完成。
如果你之前从未使用过 Grunt,强烈建议你从第一章,开始使用 Grunt开始,因为所有后续章节都将引用它。
本书面向的对象
这本书对任何希望从静态网站到更现代的 Web 应用程序构建任何东西的人都有用。一些基本的 JavaScript 经验是首选的,对 Node.js 平台的一些基本知识也可能很有帮助。
章节
在这本书中,你会发现一些频繁出现的标题(准备就绪、如何做……、它是如何工作的……、还有更多……以及另请参阅)。
为了清晰地说明如何完成一个食谱,我们使用以下章节如下:
准备就绪
这一部分会告诉你食谱中可以期待什么,并描述如何设置任何软件或任何为食谱所需的初步设置。
如何做………
这一部分包含遵循食谱所需的步骤。
它是如何工作的…
这一部分通常包含对上一节发生事件的详细解释。
更多内容…
这一部分包含有关食谱的附加信息,以便让读者对食谱有更多的了解。
相关链接
这一部分提供了对其他有关食谱的有用信息的链接。
惯例
在这本书中,你会发现许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“创建package.json文件的最简单方法是使用npm init命令。”
代码块设置如下:
{
"name": "myproject",
"version": "0.0.0",
"description": "My first Grunt project.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
当我们希望引起你对代码块中特定部分的注意时,相关的行或项目将以粗体显示:
'curl-dir': {
weather: {
router: function (url) {
var city = url.slice(url.indexOf('=') + 1, url.length);
return city + '.json';
},
src: 'http://api.openweathermap.org/'
+ 'data/2.5/weather?q={London,Paris,Tokyo}',
dest: 'weather'
}
}
任何命令行输入或输出都按以下方式编写:
Running "newer:jshint" (newer) task
Running "newer:jshint:sample" (newer) task
No newer files to process.
新术语和重要词汇以粗体显示。你在屏幕上看到的单词,例如在菜单或对话框中,在文本中显示如下:“点击下一步按钮将您带到下一屏幕。”
注意
警告或重要注意事项会出现在像这样的框中。
小贴士
小技巧和窍门如下所示。
读者反馈
我们欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢什么或不喜欢什么。读者反馈对我们来说很重要,因为它帮助我们开发出你真正能从中获得最大收益的标题。
要向我们发送一般反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书籍的标题。
如果你在某个主题上有所专长,并且你对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南,网址为www.packtpub.com/authors。
客户支持
现在你已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助你从购买中获得最大收益。
下载示例代码
你可以从你的账户下载示例代码文件,网址为www.packtpub.com,适用于你购买的所有 Packt 出版物。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给你。
错误更正
尽管我们已经尽一切努力确保我们内容的准确性,错误仍然可能发生。如果您在我们的某本书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以避免其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问 www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分下。
海盗行为
互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过链接联系我们的版权部门 <copyright@packtpub.com>,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。
第一章. 使用 Grunt 入门
在本章中,我们将涵盖以下内容:
-
安装 Grunt CLI
-
在项目中安装 Grunt
-
安装插件
-
设置基本网络服务器
-
监视文件变化
-
设置 LiveReload
-
仅处理已更改的文件
-
导入外部数据
简介
Grunt 是一个基于 Node.js 平台构建的流行的新任务自动化框架。它提供了一系列功能,允许您通过自动化重复性任务(如检查代码质量、运行测试、编译模板和代码、发布到各种类型的服务等)来简化项目工作流程,节省时间和精力。
任务自动化自软件开发之初就存在,可能被视为其存在的一个显著原因。毕竟,我们主要是编写程序来自动化重复性任务。
Grunt 本身主要是一个高度可插拔的框架,它为配置自动化任务提供了一个一致的接口。实际的任务逻辑由大量称为 插件 的模块提供,这些插件利用此框架,通常倾向于专注于某些功能集。
在撰写本文时,Grunt 项目已有超过 3 年的历史,在 npm 公共包注册表中提供了超过 3,000 个插件,并为创建或贡献现有插件项目提供了工具和指南。
目前,大量项目正在以各种方式积极使用 Grunt,其中最引人注目的是 Yeoman、Modernizr、AngularJS 和 JQuery 项目。
小贴士
如果您还没有访问过,请务必访问 Grunt 网站。那里充满了优秀的指南和文档,并且是找到您所需插件的最佳地点。网站地址如下:
安装 Grunt CLI
为了使用 Grunt 配置文件,需要安装 Grunt 的 命令行界面(CLI)工具。
命令行工具,如 Grunt CLI,通常全局安装。这意味着它们安装在您终端中当前活跃的 Node.js 安装之上,而不是在当前项目路径中,这通常是情况。
小贴士
在本书中,我们将使用 Grunt 的 0.4.x 版本,它需要 Node.js 0.8.x 或更高版本。
如何操作...
以下步骤将引导我们安装 Grunt CLI 并测试其安装是否成功。
-
假设您已经全局安装了 Node.js,以下命令用于安装 Grunt CLI:
$ npm install --global grunt-cli -
如果安装成功,
grunt命令现在应该在终端上可用。通过在您的终端中输入grunt并确认它返回的消息类似于以下内容来测试:grunt-cli: The grunt command line interface. (v0.1.13) Fatal error: Unable to find local grunt. If you're seeing this message, either a Gruntfile wasn't found or grunt hasn't been installed locally to your project. For more information about installing and configuring grunt, please see the Getting Started guide: http://gruntjs.com/getting-started
它是如何工作的...
npm install 命令会在 npm 的 公共包注册库 中查找 grunt-cli 包,一旦找到,就会下载并安装它。
使用 -g 参数与 install 命令一起表示我们想要安装的包应该全局安装,这意味着它应该安装在我们终端中当前活动的 Node.js 版本上。
在默认的 Node.js 设置中,一个用于可执行二进制文件的文件夹将自动添加为路径,该路径应由终端扫描以查找可执行命令。这使得在安装此包后,grunt 命令自动可用,因为其可执行二进制文件由包的安装信息提供并指示。
在项目中设置 Grunt
为了使项目能够使用 Grunt 框架,它将需要安装其库并设置一个最基本配置文件。库提供了所有 Grunt 插件所需的框架和工具,而配置文件提供了一个起点,从这里我们可以开始加载插件并调整其行为。
准备工作
对于一个项目来说,以某种方式打包以帮助跟踪依赖项、二进制文件、脚本、维护者和其他重要信息通常是一个好主意。基于 Node.js 的项目的标准包格式是 CommonJS。
小贴士
要了解更多关于 CommonJS 的信息,您可以查看以下网址中的其规范:
wiki.commonjs.org/wiki/Packages/1.1
CommonJS 包的核心是 package.json 文件。该文件包含关于包的所有重要信息,并以 JSON 格式存储。创建 package.json 文件的最简单方法就是使用 npm init 命令。该命令会提出一系列问题,并根据提供的答案生成一个 package.json 文件。以下是在运行该命令时提出的问题示例:
name: (grunt-book) myproject
version: (0.0.0)
description: My first Grunt project.
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
在回答这些问题之后,将在当前路径下生成一个包含以下内容的 package.json 文件:
{
"name": "myproject",
"version": "0.0.0",
"description": "My first Grunt project.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
小贴士
在以下网址可以找到关于 package.json 文件的另一个实用指南:
如何操作...
以下步骤将指导我们在项目中安装 Grunt 框架库并创建一个最基本配置文件。
-
首先,我们将安装 Grunt 库到当前项目路径中,并将其添加到我们的项目依赖中。所有这些都可以通过以下命令完成:
$ npm install --save grunt小贴士
由于我们在
install命令中使用了--save标志,Grunt 包将被添加到项目包的依赖列表中。这可以通过查看package.json文件中的dependencies属性来确认。当你想将安装的包添加到
devDependencies属性中时,可以使用--save-dev标志与install命令一起使用,该属性列出了设置开发环境所需的依赖项。 -
接下来,我们将设置一个空的配置文件,它至少可以允许 Grunt 运行,并为未来的任务配置提供一个位置。让我们在我们的项目根目录中创建一个名为
Gruntfile.js的文件,其内容如下:module.exports = function (grunt) { grunt.initConfig({}); grunt.registerTask('default', []); }; -
现在我们已经安装了 Grunt 库并设置了基本的配置文件,我们可以使用
grunt命令来测试它是否按预期工作。在终端中运行grunt命令现在应该产生类似于以下内容的输出:Done, without errors.小贴士
不指定任何参数运行
grunt命令时,总是会尝试运行default任务,在我们的当前示例中,它被设置为不执行任何操作。
它是如何工作的...
当使用 Grunt CLI 工具时,它总是寻找最近的名为Gruntfile.js的文件,然后尝试从中加载配置。在配置文件中,有一个导出的函数接收一个参数。这个参数是一个对象,它为我们提供了访问 Grunt 框架以加载、创建和配置任务的权限。
到目前为止,我们没有加载或创建任何任务,也没有定义任何配置。我们的default任务也被设置为不执行任何操作,因此运行grunt命令除了报告它已成功完成外,没有做任何事情。
安装插件
Grunt 可以提供的所有功能都包含在以 Node.js 包形式提供的插件中。在本食谱中,我们将通过安装插件的流程来准备我们后续的所有食谱。
在我们的示例中,我们将安装contrib-jshint (0.10.0)插件。安装此插件所使用的相同步骤可以用来安装插件包索引中提供的任何其他插件。
准备工作
在本例中,我们将使用我们在本章的在项目中设置 Grunt食谱中创建的基本项目结构。如果你还不熟悉它的内容,请务必参考它。
如何操作...
以下步骤将指导我们在项目中安装插件并加载它包含的任务:
-
安装插件的第一步是在当前项目路径中安装包含它的包。在我们的示例中,我们将安装包含在
grunt-contrib-jshint包中的contrib-jshint插件。我们可以使用以下命令安装此包并将其添加到我们的项目依赖项中:$ npm install --save grunt-contrib-jshint -
接下来,我们需要加载插件包中包含的任务,以便它们可以在我们的配置中使用。这是通过使用
loadNpmTasks函数完成的,该函数由传递给配置文件的grunt对象提供。添加此功能后,我们的配置文件应类似于以下内容:module.exports = function (grunt) { grunt.initConfig({}); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.registerTask('default', []); }; -
现在我们已经安装了包并加载了其任务,我们可以在配置中使用这些加载的任务。在我们的例子中,我们加载了
jshint任务,这使得我们能够以类似以下方式使用它:module.exports = function (grunt) { grunt.initConfig({ jshint: { sample: { files: 'src/*.js' } } }); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.registerTask('default', []); };
更多内容...
随着你开始使用越来越多的 Grunt 插件,你很快就会想知道是否有办法优化这个过程。幸运的是,有人已经在这条路上走在了前面,并创建了 load-grunt-tasks 工具,该工具自动从你项目依赖中提到的所有包中加载任务。这意味着我们不再需要为每个安装的插件添加 loadNpmTasks 调用。
以下步骤说明了此实用程序的用法,从我们在主菜谱中早期完成的工作继续:
-
在当前项目路径中安装此实用程序的包,并使用以下命令将其添加到依赖项中:
$ npm install --save load-grunt-tasks -
现在我们可以通过导入包并将
grunt对象传递给它来在我们的配置文件中使用它。现在我们正在使用这个实用程序,我们也可以删除我们用来加载插件的loadNpmTasks。module.exports = function (grunt) { require('load-grunt-tasks')(grunt); grunt.initConfig({ jshint: { sample: { files: 'src/*.js' } } }); grunt.registerTask('default', []); };小贴士
默认情况下,
load-grunt-tasks插件只会加载以grunt开头的插件名称。此行为可以通过使用pattern选项进行自定义。要了解更多关于load-grunt-tasks插件的信息,请参考以下网址中的插件页面:
设置基本 Web 服务器
在基于 Web 的项目的开发过程中,简单的 Web 服务器总是很有用。它们可以轻松设置并用于从你的本地机器提供 Web 内容,这样你就不必担心不断将你的实验性更改部署到远程服务提供商。
我们将使用 contrib-connect (0.8.0) 插件,它为我们提供了基于 Connect 服务器框架设置和运行简单 Web 服务器的功能。默认情况下,它将只从目录中提供文件,但它还有一个额外的优点,即能够利用许多可用的 Connect 中间件插件。
小贴士
你可以在以下网址了解更多关于 Connect 服务器框架及其中间件插件的信息:
准备工作
在本例中,我们将使用本章中 在项目中设置 Grunt 菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们设置一个开发服务器,该服务器从位于我们项目目录中的目录中提供文件。
-
我们将首先安装包含
contrib-connect插件的包,并按照本章“安装插件”食谱中提供的说明加载其任务。 -
从插件中加载
connect任务后,我们可以在配置中使用它。为此,我们在配置中添加以下内容:connect: { server: { options: { base: 'www', keepalive: true } } }小贴士
base选项表示服务器应在哪个目录中查找请求的文件。您希望与开发服务器一起提供的所有内容都可以放置在这个目录中。这包括 HTML 页面、JavaScript 源文件、样式表、图像等等。keepalive选项在请求的任务完成后仍保持服务器运行。如果您单独运行connect任务,这通常是首选的,但如果在它完成后还有其他任务将无限期运行,则不是必需的。 -
让我们添加一个简单的文件,这样我们就可以从我们的服务器上提供它,以便我们可以用它来测试。在项目根目录下创建一个名为
www的目录,然后在其中创建一个名为index.html的文件,内容如下:<html> <head> <title>Test Page</title> </head> <body> <h1>This is a test page.</h1> </body> </html>小贴士
与许多其他网页服务器一样,此任务启动的 Connect 服务器将始终在请求 URL 未指定文件名的情况下在文件夹中查找
index.html文件。 -
现在,我们可以使用
grunt connect命令来运行我们的网页服务器,这将产生输出,表明服务器已启动,以及可以访问的 URL:Running "connect:server" (connect) task Waiting forever... Started connect web server on http://0.0.0.0:8000 -
最后,我们可以使用我们最喜欢的浏览器访问输出中提到的 URL。这将显示我们的示例页面,通过运行的服务器提供:
![如何操作...]()
更多内容...
connect 任务提供了许多有用的配置选项,允许我们自动打开浏览器、指定服务器应运行的端口和主机名、从多个目录提供文件、使用其他 Connect 中间件插件以及向创建的服务器添加额外功能。
在默认 URL 上打开默认网页浏览器
为了在默认的 URL 上自动打开我们喜欢的网页浏览器,我们可以将 open 选项设置为 true,就像以下示例中那样:
options: {
base: 'www',
keepalive: true,
open: true
}
在特定 URL 打开特定网页浏览器
当我们想要使用默认浏览器以外的其他浏览器打开非默认 URL 时,我们可以向 open 选项提供一个对象,该对象指定了我们确切想要的内容。以下代码指定了一个 URL、用于打开它的浏览器以及一旦打开就应调用的回调函数:
options: {
base: 'www',
keepalive: true,
open: {
target: 'http://localhost:8000/test.html',
appName: 'firefox',
callback: function() {
console.log('Test URL opened in Firefox!');
}
}
}
小贴士
下载示例代码
您可以从您在 www.packtpub.com 的账户下载示例代码文件,以获取您购买的所有 Packt Publishing 书籍。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。
使用特定端口
连接任务默认使用的端口是8000,但如果你想要使用不同的端口,可以使用port选项来指定你想要的端口,如下面的代码片段所示:
options: {
port: 3000,
base: 'www',
keepalive: true
}
自动选择可用端口
如果你在启动服务器时不确定哪些端口可用,让服务器自动选择一个开放的端口会非常有用。将useAvailablePort选项设置为true将启用此行为。以下是这个行为的代码片段:
options: {
base: 'www',
keepalive: true,
useAvailablePort: true
}
使用特定主机名
如果你希望服务器连接到特定的主机名而不是默认的0.0.0.0,你可以使用以下方式利用hostname选项:
options: {
base: 'www',
keepalive: true,
hostname: 'something.else.com'
}
从多个目录中提供文件
如果你有多于一个目录包含你想要提供服务的文件,那么你可以提供一个包含目录名称的数组给base选项来查找内容。以下是一个代码片段供你参考:
options: {
base: ['www', 'other'],
keepalive: true
}
小贴士
当使用数组作为base选项时,服务器将从左到右在每个目录中查找请求的资源,一旦找到就返回资源。如果示例中的两个目录都包含一个index.html文件,对根 URL 的请求将返回www目录中的index.html文件。
使用中间件
如果我们想要使用 Connect 框架提供的许多现有中间件插件之一,我们可以将middleware选项设置为函数,该函数通过向其中添加所需的中间件来修改中间件堆栈。
-
首先,我们需要安装我们想要使用的中间件,在我们的例子中,这个中间件是和 Connect 服务器框架一起打包的。我们可以使用以下命令来安装框架包:
$ npm install --save connect -
现在,我们修改
connect任务中server目标的选项,以便它将compress中间件添加到堆栈中:options: { base: 'www', keepalive: true, middleware: function(connect, options, middlewares) { middlewares.push( require('connect').middleware.compress() ); return middlewares; } }小贴士
middleware选项也可以设置为数组,但这将替换connect任务提供的默认堆栈。默认的中间件允许从由base选项指定的目录中提供文件服务。
向创建的服务器添加功能
有时候,与connect任务创建的服务器一起工作会有所帮助。一个很好的例子是我们想要让我们的服务器能够处理Socket.IO交互。这可以通过向onCreateServer选项提供一个函数来实现,该函数以任何你喜欢的方式与创建的服务器一起工作:
options: {
base: 'www',
keepalive: true,
onCreateServer: function(server, connect, options) {
var io = require('socket.io').listen(server);
io.sockets.on('connect', function (socket) {
// do something with socket
});
}
}
小贴士
此示例假设你已经安装了socket.io包。你可以在其网站上了解更多关于socket.io的信息:
监视文件变化
开发环境中常见的另一个需求是,当某些文件发生变化时自动运行特定任务。这在您希望实时监控代码质量或一旦资源更改就重新编译它们,以便更改的效果无需手动干预时特别有用。
contirb-watch (0.6.1) 插件允许我们在观察到文件事件时监视一组特定的文件,并运行一组指定的任务。
准备工作
在本例中,我们将使用我们在本章的 在项目中设置 Grunt 菜单中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何做这件事...
以下步骤将引导我们设置一个 watch 任务,每次观察到文件更改时,都会在 JavaScript 源文件上启动代码质量分析。
-
我们将首先按照本章 安装插件 菜单中提供的说明安装包含
contrib-watch插件的包,并加载其任务。 -
对于我们的示例,我们将使用
jshint任务来分析 JavaScript 源文件的质量。让我们按照本章 安装插件 菜单中提供的说明安装contrib-jshint插件并加载其任务。 -
我们还需要一个 JavaScript 源文件,我们可以监视其更改并对其进行质量分析。让我们在我们的项目根目录中创建一个名为
sample.js的文件,并为其提供以下内容:var sample = 'Sample'; console.log(sample); -
现在,我们可以设置一个示例
jshint任务,我们将通过添加以下内容到我们的配置中,使用watch任务来运行它:jshint: { sample: { src: ['sample.js'] } } -
在安装插件并配置示例任务后,我们现在可以配置
watch任务的目标,每次名为sample.js的样本文件更改时,都会运行jshint任务。这是通过添加以下内容到我们的配置中实现的:watch: { sample: { files: ['sample.js'], tasks: ['jshint'] } } -
最后,我们可以使用
grunt watch命令启动任务,这将产生以下输出以确认它正在运行:Running "watch" task Waiting... -
为了测试我们的设置,我们现在可以修改
sample.js文件并保存更改。这应该会产生类似以下内容的输出,告知我们文件事件:Running "watch" task Waiting... >> File "sample.js" changed. Running "jshint:sample" (jshint) task >> 1 file lint free. Done, without errors. Completed in 1.0s at Wed Jan 1 2014 00:00:00 GMT - Waiting...
还有更多...
watch 任务插件提供了许多有用的配置选项,允许我们监视多个文件,运行一系列任务,防止任务运行时进程的创建,启用任务运行的中断,指定重新运行任务前的等待时间,仅在特定文件事件上运行任务,允许任务杀死监视进程,以及当监视器启动时运行任务一次。
监视多个文件
如果我们想监视多个文件,可以使用标准 Grunt files配置的模式匹配能力。以下配置示例将监视项目根目录或其任何子目录中具有txt扩展名的所有文件:
watch: {
sample: {
files: ['**/*.txt'],
tasks: ['sample']
}
}
小贴士
你可以在以下网址了解更多关于文件配置以及它所支持的 globbing 模式的信息:
gruntjs.com/configuring-tasks#files
运行一系列任务
如果我们想在观察文件事件时每次运行多个任务,我们只需将任务添加到传递给tasks配置的数组中即可:
watch: {
sample: {
files: ['sample.txt'],
tasks: ['sample', 'another', 'finally']
}
}
小贴士
使用tasks配置指定的任务将按它们在数组中放置的顺序逐个运行。
防止任务运行时进程的创建
watch任务的默认行为是在各自的子进程中启动每个被触发的任务。这防止了触发的任务失败导致watch任务本身失败。作为副作用,它还会为每个任务克隆监视器进程的上下文。然而,可以通过将spawn选项设置为false来禁用此行为,这会使任务启动得更快,并允许它们之间共享上下文。以下展示了这种配置的示例:
watch: {
sample: {
files: ['sample.txt'],
tasks: ['sample'],
options: {
spawn: false
}
}
}
启用任务运行的中断
监视器的默认行为是在等待由前一个更改触发的任务完成之后,再次等待更改。通过将interrupt选项设置为true,当检测到更改时,监视器将停止运行任务并重新启动它们。以下展示了这种配置的示例:
watch: {
sample: {
files: ['sample.txt'],
tasks: ['sample'],
options: {
interrupt: true
}
}
}
指定在重新运行任务之前的等待期
监视器在之前任务运行后检查文件更改的默认等待期是500ms。这个时间可以通过将debounceDelay选项设置为所需的毫秒数来更改。以下展示了这种配置的示例:
watch: {
sample: {
files: ['sample.txt'],
tasks: ['sample'],
options: {
debounceDelay: 1000
}
}
}
仅在特定的文件事件上运行任务
除了更改之外,文件还可以被添加和删除。监视器的默认行为是观察所有这些事件,但如果只想在特定事件上运行任务,可以将event选项设置为changed、added、deleted或all。
以下示例将仅在名为sample.txt的文件被添加或删除到与配置文件相同路径的情况下启动sample任务:
watch: {
sample: {
files: ['sample.txt'],
tasks: ['sample'],
options: {
event: ['added', 'deleted']
}
}
}
允许任务终止监视器进程
由监视器启动的任务引发的警告和失败,默认情况下不会中断其执行。将forever选项设置为false将禁用此行为,并允许子任务在警告和失败时停止监视器进程。以下展示了这种配置的示例:
watch: {
options: {
forever: false
},
sample: {
files: ['sample.txt'],
tasks: ['sample']
}
}
小贴士
注意,forever选项仅是任务级别的选项,不能为单个目标指定。
监视器启动时运行任务一次
如果您希望在监视器启动后立即运行tasks选项中指定的任务,而不仅仅是观察文件事件时才运行,可以将atBegin选项设置为true。以下展示了这种配置的示例:
watch: {
sample: {
files: ['sample.txt'],
tasks: ['sample'],
options: {
atBegin: true
}
}
}
设置 LiveReload
一旦您运行了一个提供构成您的 Web 应用程序的页面、代码和资源的开发服务器,您将注意到每次您希望观察所做的更改时,都必须刷新浏览器。
这就是LiveReload工具及其组成库发挥作用的地方。它旨在在页面或代码文件更改时自动重新加载浏览器内容,甚至可以在不刷新浏览器内容的情况下实时应用 CSS 和图像更改。
我们可以使用本章其他部分讨论的两个插件来为我们的项目设置 LiveReload。由contrib-connect (0.8.0)插件提供的开发服务器可以被配置为接受 LiveReload 触发器,而contrib-watch (0.6.1)插件用于发送它们文件事件。
准备工作
在这个例子中,我们将使用本章在项目中设置 Grunt配方中创建的基本项目结构。如果您还不熟悉它的内容,请务必参考它。
在这个配方中,我们还使用了contrib-connect和contrib-watch插件,它们已经在本章的设置基本 Web 服务器和监视文件变化配方中分别进行了讨论。如果您还不熟悉它们讨论的内容,请参考这些配方。
如何操作...
以下步骤将引导我们设置一个监视任务,当它观察到从本地开发服务器提供的 HTML 页面发生变化时,将自动触发刷新。
-
我们将首先按照本章安装插件配方中提供的说明安装包含
contrib-connect和contrib-watch插件的包,并通过加载它们的任务。 -
为了我们的示例,我们将在
www目录中创建一个名为index.html的文件,这是我们希望在浏览器中查看并在对其做出更改时自动更新的文件。该文件的以下内容如下:<html> <head> <title>LiveReload Test</title> </head> <body> <h1>First there was this.</h1> </body> </html> -
接下来,我们将设置我们的开发服务器,它将从
www目录中提供index.html文件。除了标准配置外,我们还将livereload选项设置为true,表示开发服务器应启用浏览器接收 LiveReload 触发器。所有这些操作都是通过向我们的配置中添加以下内容来完成的:connect: { dev: { options: { base: 'www', livereload: true } } }小贴士
将
livereload选项的值设置为true会将connect-livereload包含在 connect 服务器的中间件堆栈中。中间件随后会在所服务的页面的 HTML 代码中插入一段代码,使浏览器能够接受 LiveReload 触发器。由于监视器进程将在服务器启动后继续运行,因此可以排除
keepalive选项。这意味着 Grunt 进程不会结束,这也会停止它启动的服务器。 -
现在,我们将设置一个监视器来观察
www/index.html文件中的文件事件。除了标准配置外,我们还将livereload选项设置为true,表示每当观察到更改时,应发送适当的 LiveReload 触发器。这是通过向我们的配置中添加以下内容来完成的:watch: { www: { files: ['www/index.html'], options: { livereload: true } } } -
最后,我们可以使用
grunt connect watch命令启动我们的服务器和监视器,这将产生指示两者启动的输出:Running "connect:dev" (connect) task Started connect web server on http://0.0.0.0:8000 Running "watch" task Waiting... -
现在,我们可以使用我们最喜欢的浏览器打开输出中提到的 URL,它应该显示由服务器提供的我们的示例页面:
![如何操作...]()
-
让我们通过更改
www/index.html文件并保存它来尝试 LiveReload 功能。这个动作应该在启动服务器和监视器的终端中产生以下输出:>> File "www/index.html" changed. Completed in 0.001s at Wed Jan 01 2014 00:00:00 GMT – Waiting... -
切换回我们当前打开
http://0.0.0.0:8000URL 的浏览器,我们现在应该看到更新的页面,而无需手动刷新:![如何操作...]()
仅处理已更改的文件
当运行以某种方式处理文件的任务时,你很快就会意识到你可能不希望它在每次执行时处理所有文件。当任务必须处理的文件数量变得相当大时,这一点尤其正确。
在这种情况下,newer (0.7.0)插件可以通过确保每次调用运行时只处理自任务上次运行以来更改的文件来提供帮助。它可以与任何使用标准files配置的插件一起使用,并且当使用监视器在检测到文件更改时重新运行任务时变得特别有用。
准备中
在本例中,我们将使用本章中“在项目中设置 Grunt”食谱中创建的基本项目结构。如果你还不熟悉它的内容,请务必参考它。
如何操作...
以下步骤将引导我们使用newer插件来检查自上次运行以来更改的 JavaScript 源代码文件的代码质量。
-
我们将首先安装包含
newer插件的包,并按照本章中“安装插件”食谱中提供的说明加载其任务。 -
在我们的示例中,我们还将安装
contrib-jshint插件,并按照本章中“安装插件”食谱中提供的说明加载其任务。 -
在安装了所有必需的插件后,我们现在可以为
jshint任务添加示例配置,该任务将对src目录中的所有 JavaScript 文件执行代码质量检查。这是通过向我们的配置中添加以下内容来完成的:jshint: { sample: { src: 'src/**/*.js' } }小贴士
注意,
jshint插件要求你在目标内部的src配置中直接指定任务要针对的文件。在其他情况下,通常建议使用files配置来指定你的目标文件。 -
现在,我们可以通过在终端中使用
grunt jshint命令运行jshint任务几次来观察其行为。每次运行它,我们都会看到它扫描src目录中的所有文件。这应该每次都产生相同的输出,类似于以下内容:Running "jshint:sample" (jshint) task >> 3 files lint free. -
为了使用
newer插件,我们在我们希望运行的任务的名称前添加newer:。在第一次运行grunt newer:jshint命令时,newer插件将缓存已处理文件的最后修改时间戳。这会产生类似于以下内容的输出:Running "newer:jshint" (newer) task Running "newer:jshint:sample" (newer) task Running "jshint:sample" (jshint) task >> 3 files lint free. Running "newer-postrun:jshint:sample:.cache" (newer-postrun) task -
当我们再次运行
grunt newer:jshint命令时,我们会看到没有文件被jshint任务处理,这会产生类似于以下内容的输出:Running "newer:jshint" (newer) task Running "newer:jshint:sample" (newer) task No newer files to process. -
现在,我们可以更改
src目录中的一个文件,并再次运行命令以查看更改的文件是否再次被处理:Running "newer:jshint" (newer) task Running "newer:jshint:sample" (newer) task Running "jshint:sample" (jshint) task >> 1 file lint free. Running "newer-postrun:jshint:sample:1:.cache" (newer-postrun) task
还有更多...
当使用contrib-watch插件提供的watch任务时,仅处理更改的文件变得特别有用。watch任务会在每次观察到文件更改时重新运行其指示的任务,这在开发过程中可能会相当频繁,如果任务针对大量文件,可能会花费相当多的时间。
以下步骤提供了一个如何结合使用newer插件和contrib-watch的示例,并继续我们在主要配方中完成的工作:
-
我们将首先按照本章中“安装插件”配方中提供的说明安装包含
contrib-watch插件的包,并通过加载其任务来使用它。 -
现在,我们将添加一个
watch任务,当它观察到src目录中包含的任何 JavaScript 文件发生变化时,将运行jshint任务。我们还将使用newer:前缀来预置jshint任务,表示我们只想处理实际更改的文件。这是通过在我们的配置中添加以下内容来实现的:watch: { jshint: { files: ['src/**/*.js'], tasks: ['newer:jshint'] } } -
现在,我们可以通过在终端中使用
grunt watch命令来启动watch任务。这应该会产生类似于以下内容的输出,表明监视器正在运行:Running "watch" task Waiting... -
如果我们现在在
src目录下更改并保存一个文件,我们应该看到类似以下输出,表明只有更改的文件被jshint任务处理过:>> File "src/file.js" changed. Running "newer:jshint" (newer) task Running "newer:jshint:sample" (newer) task Running "jshint:sample" (jshint) task >> 1 file lint free. Running "newer-postrun:jshint:sample:1:.cache" (newer-postrun) task
导入外部数据
在大多数编码实践中,最佳实践是尽可能地将逻辑和数据分离。同样的规则也适用于 Grunt 配置逻辑以及它所使用的数据。
一个非常常见的使用外部数据的情况是使用包含在package.json文件中的项目信息。诸如项目名称和版本号之类的信息可能不会太经常改变,但一旦它们改变,我们可能更愿意不想在项目的每个地方都去找它们。
幸运的是,Grunt 框架为我们提供了允许我们轻松从外部文件读取数据并将其存储在配置中的函数。然后,这些存储的数据也可以通过自动处理所有配置的字符串模板轻松使用。
准备工作
在本例中,我们将使用本章中“在项目中设置 Grunt”配方中创建的基本项目结构。如果您还不熟悉它的内容,请务必参考它。
如何操作...
以下步骤将指导我们如何在使用package.json文件中的数据生成 JavaScript 源文件的优化版本时使用它。
-
在我们的例子中,我们将使用
contib-uglify插件,它可以用来压缩 JavaScript 源文件。让我们按照本章中“安装插件”配方中提供的说明来安装它并加载其任务。 -
为了本例的需要,我们还需要一个简单的 JavaScript 源文件。让我们在我们的项目目录根目录下创建一个名为
sample.js的文件,并用以下代码填充它:module.exports = function () { var sample = 'Sample'; console.log(sample); }; -
接下来,我们将通过使用
grunt.file.readJSON函数并将其结果分配给配置中名为pkg的属性,来导入我们项目中的pacakge.json文件中的数据。添加此之后,我们的配置对象应类似于以下内容:{ pkg: grunt.file.readJSON('package.json') }小贴士
注意,属性名
pkg仅用于本例,几乎可以是任何名称,除了配置中可用的任务名称。 -
现在我们已经从我们的项目包中导入了数据,我们可以设置一个
uglify任务,该任务将使用package.json文件中包含的版本号作为结果文件名称的一部分来压缩名为sample.js的 JavaScript 源文件。这是通过向我们的配置中添加以下内容来完成的:uglify: { sample: { files: { 'sample_<%= pkg.version %>.js': 'sample.js' } } }小贴士
Grunt 使用Lo-Dash字符串模板系统。您可以在以下 URL 了解更多信息:
-
最后,我们可以使用
grunt uglify命令来测试我们的设置,这应该会产生类似于以下内容的输出:Running "uglify:sample" (uglify) task File sample_0.0.0.js created: 81 B → 57 B -
我们现在也可以查看我们新创建的压缩版本的
sample.js文件,该文件应位于sample_0.0.0.js中,其内容应类似于以下内容:module.exports = function(){var a="Sample";console.log(a)};
更多内容...
YAML 格式提供了另一种存储人类可读数据并可以轻松导入到我们的配置中的流行方式。以下基于我们在主要配方中所做的工作的示例演示了此功能:
-
首先,我们将创建一个简单的 YAML 文件,用于我们的示例。让我们在我们的项目根目录中创建
sample.yaml,并给它以下内容:version: 0.0.0 -
现在我们需要做的就是将调用
grunt.file.readJSON改为导入我们的样本 YAML 文件。我们通过将pkg配置更改为以下内容来实现这一点:pkg: grunt.file.readYAML('sample.yaml') -
如果我们现在运行 Grunt 的
uglify命令,我们应该看到与之前相同的结果,输出类似于以下内容:Running "uglify:sample" (uglify) task File sample_0.0.0.js created: 81 B → 57 B
第二章。文件管理
在本章中,我们将介绍以下食谱:
-
复制文件
-
压缩文件
-
创建符号链接
-
合并文件
-
获取单个 URL
-
获取多个 URL
简介
在本章中,我们将专注于使用 Grunt 来处理我们的日常文件管理任务。很少有日子不涉及复制、压缩、下载或合并文件,借助 Grunt,我们可以自动化这些简单的任务。
复制文件
在我们追求项目自动化终极目标的过程中,我们很快就会想要自动化文件或目录从一个项目部分到另一个部分的复制。contrib-copy(0.5.0)插件为我们提供了这一功能,以及一些在复制文件过程中特别有用的其他选项。
准备工作
在此示例中,我们将使用我们在第一章中创建的基本项目结构,即第一章的在项目中安装 Grunt食谱。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们从一个目录复制单个文件到另一个目录。
-
我们将按照第一章中安装插件食谱中提供的说明安装包含
contrib-copy插件的包。 -
让我们在我们的项目中设置一个示例文件,我们可以通过复制操作来尝试它。在
src目录中创建一个名为sample.txt的文件,其内容如下:Some sample data. -
现在,我们可以添加我们的
copy任务配置,这将指导它将我们的示例文件从src目录复制到dest目录:copy: { sample: { src: 'src/sample.txt', dest: 'dest/sample.txt' } }小贴士
注意,在复制操作中指定的目标目录如果尚不存在,将被自动创建。
-
最后,我们可以运行
grunt copy命令,它应该会提供输出,告知我们复制操作成功:Running "copy:files" (copy) task Copied 1 files -
为了确认示例文件已被复制,我们现在可以使用我们最喜欢的编辑器打开
dest/sample.txt文件,并确认其内容与原始文件的内容匹配。
更多...
copy任务提供了一些有用的配置选项,允许我们处理正在复制的文件的内容,复制源文件的权限,并设置目标文件的权限。
处理文件内容
copy任务提供了在复制文件时更改文件内容的选项。这个功能有许多有用的应用,例如渲染模板或替换与正则表达式匹配的文件部分。
在以下示例中,我们将简单地关注在复制的文件前添加一个字符串。我们将在此基础上构建我们在此食谱中早些时候的工作。
-
我们将首先提供一个
process选项的占位函数,该函数只是传递内容而不做任何修改。这将改变我们copy任务的配置,如下所示:copy: { sample: { options: { process: function (content, path) { return content; } }, src: 'src/sample.txt', dest: 'dest/sample.txt' } } -
现在,我们可以修改传递给
process选项的函数,以便在正在复制的文件内容前添加一个字符串,使其看起来类似于以下内容:process: function (content, path) { return 'Prepended string!\n' + content; } -
如果我们现在再次运行
grunt copy命令,我们将看到文件正在被复制的确认信息,并且查看dest/sample.txt文件的内容,我们应该看到以下内容:Prepended content Sample content.
复制源文件的权限
文件附加的权限通常非常重要。默认情况下,复制任务将忽略源文件的文件权限,但如果你希望它们应用于目标文件,则可以将 mode 选项设置为 true,如下例所示:
copy: {
sample: {
options: {
mode: true
},
src: 'src/sample.txt',
dest: 'dest/sample.txt'
}
}
设置目标文件的权限
如果你希望指定复制操作的目标文件应获得的权限,你可以通过将 mode 选项设置为所需的权限模式,使用传统的八进制格式的 Unix 权限来实现。
小贴士
你可以在 en.wikipedia.org/wiki/File_system_permissions 上阅读有关传统 Unix 权限约定的更多信息。
此外,还有一个方便的 Unix 文件权限计算器,可在 permissions-calculator.org/ 找到。
以下示例配置将使目标文件只对其所有者可读和可写:
copy: {
files: {
src: 'src/sample.txt',
dest: 'www/sample.txt',
options: {
mode: '0600'
}
}
}
文件压缩
在我们的本地系统以及整个万维网上,文件压缩是一种相当常见的做法。文件压缩最显著的应用是减少将通过网络传输的数据大小。
为了我们自己的目的,我们可能在某个时候想要自动化压缩一个我们希望可供下载或准备传输的文件或文件夹。contrib-compress (0.10.0) 插件为我们提供了这种功能,以及指定在过程中使用的存档类型和算法的选项。
准备工作
在本例中,我们将使用我们在 第一章 中 在项目中安装 Grunt 菜单创建的基本项目结构。如果你还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导我们使用 compress 任务的默认设置压缩单个样本文件。
-
我们将按照 第一章 中 安装插件 菜单提供的说明,安装包含
contrib-compress插件的包,使用 Grunt 入门。 -
然后,我们可以创建一个示例文件来演示压缩功能。让我们下载一本文本格式的书籍作为示例,并将其保存为
book.txt在我们的项目目录中。小贴士
简·奥斯汀的《傲慢与偏见》的文本版本可以从以下 URL 下载:
-
现在,我们可以为
compress任务添加以下配置,这将把我们的示例文件压缩到book.txt.gz归档文件中:compress: { sample: { src: 'book.txt.gz', dest: 'book.txt' } } -
为了尝试它,我们可以运行
grunt compress命令,它应该产生类似于以下输出的结果:Running "compress:sample" (compress) task Created book.txt.gz (314018 bytes) -
如果我们查看
book.txt.gz文件,我们会看到它的大小大约是book.txt文件的一半。我们还可以进一步使用我们喜欢的归档提取器提取此文件,以确认它包含原始文件。
还有更多...
compress任务为我们提供了几个选项,允许我们归档一组文件,指定压缩模式,以及指定归档压缩级别。
归档一组文件
在我们的主要配方中,我们压缩了一个单独的文件,但将一组文件归档成一个单独的文件可能更为常见。为了做到这一点,我们修改src配置以指定应归档的文件,删除dest配置,然后使用archive选项提供一个归档文件的名称。
以下步骤继续我们在此配方中早期所做的操作,直到归档并将一组文件压缩成一个单独的文件。
-
首先,我们将添加一些我们希望归档和压缩的更多文件。让我们将之前下载的书籍的名称从
book.txt更改为austen.txt,并下载另外两本,分别重命名为wells.txt和thoreau.txt。小贴士
提到的两个示例书籍可以从以下 URL 按提到的顺序下载:
-
现在,我们可以更改
compress任务的配置,从仅压缩名为book.txt的文件,到包括所有三个下载的书籍在一个名为books.zip的归档文件中。我们通过更改其配置如下来实现:compress: { sample: { options: { archive: 'books.zip' }, src: ['austen.txt', 'wells.txt', 'thoreau.txt'] } } -
让我们通过运行
grunt compress命令来尝试它,它应该提供类似于以下输出的结果:Running "compress:sample" (compress) task Created books.zip (786932 bytes) -
我们现在应该在项目目录中看到
books.zip存档。为了确认它包含我们指定的所有文件,您可以使用您喜欢的归档工具来探索或提取它。
指定压缩模式
默认情况下,compress任务将根据在archive选项中指定的文件名的扩展名来确定其操作中使用的压缩模式。可以通过使用mode选项来改变这种行为。
以下示例将使用zip模式,尽管在archive选项中指定的文件扩展名是tgz:
compress: {
sample: {
options: {
mode: 'zip',
archive: 'books.tgz'
},
files: {
src: ['austen.txt', 'wells.txt', 'thoreau.txt']
}
}
}
小贴士
在出版时,mode选项支持的压缩模式有gzip、deflate、deflateRaw、tar、tgz和zip。
指定存档压缩级别
如果我们想在使用zip或gzip压缩模式时指定压缩率,我们可以使用level选项来做到这一点。该选项默认设置为1,但可以设置为从1到9的任何整数,后者表示更好的压缩效果,但性能会稍慢。
以下配置将把我们的书籍压缩到9级,这会花费一点时间,但可以将生成的存档大小减少大约 10%:
compress: {
sample: {
options: {
level: 9,
archive: 'books.zip'
},
src: ['austen.txt', 'wells.txt', 'thoreau.txt']
}
}
创建符号链接
在各种情况下创建文件和目录的引用可能非常有用,尤其是在我们想要分发文件的副本并保持它们更新,而无需在每次更改后手动复制它们时。
contrib-symlink (0.3.0)插件为我们提供了创建符号链接的功能。这是一个非常简单的插件,但它提供了所有标准的 Grunt 配置选项。
准备工作
在本例中,我们将使用在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即使用 Grunt 入门。如果你还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导我们创建指向www/img目录内名为assets/img/logo.png的文件的符号链接。
-
我们将按照第一章中“安装插件”配方中的说明来安装包含
contrib-symlink插件的包。 -
然后,我们可以添加以下配置,它将在
img/www目录中创建符号链接:symlink: { sample: { files: { 'www/img/logo.png': 'assets/img/logo.png' } } } -
要尝试它,我们可以运行
grunt symlink命令,它应该会通知我们符号链接的创建,输出类似于以下内容:Running "symlink:files" (symlink) task >> Created 1 symbolic links. -
如果你现在查看
www/img目录,你应该会看到一个名为logo.png的符号链接,它指向assets/img/logo.png文件。也许你还可以尝试更改图片的内容,以查看它们在符号链接中的反映。
文件连接
将文件连接起来的实践在所有类型的开发者中都很常见。无论是将分散的日志文件或许多较小的源文件合并到一个大的代码库中,文件端到端连接在日常工作中非常有用。
连接功能可以通过contrib-concat (0.4.0)插件提供。除了标准的 Grunt 配置外,它还提供了一组选项,以便根据我们的独特需求定制其行为。
准备中
在此示例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们将三个 JavaScript 源文件连接到一个组合源文件中,这样我们只需要与我们的 Web 应用程序一起提供单个源文件。
-
我们将按照第一章中“安装插件”配方提供的说明,安装包含
contrib-concat插件的包,该配方位于使用 Grunt 入门。 -
对于我们的示例,我们还需要三个 JavaScript 源文件。让我们称它们为
one.js、two.js和three.js,并为每个文件提供以下内容,只需将<filename>替换为它们的名称:var sample = 'Sample code from <filename>.'; console.log(sample); -
现在,我们可以为
concat任务添加以下配置,该配置将把我们的三个样本文件合并到all.js文件中:concat: { sample: { files: { 'all.js': ['one.js', 'two.js', 'three.js'] } } } -
要尝试我们的任务,我们可以运行
grunt concat命令,这将产生类似于以下内容的输出:Running "concat:sample" (concat) task File all.js created -
现在,我们可以查看创建的
all.js文件,其中将包含以下代码:var sample = 'Sample code from one.js.'; console.log(sample); var sample = 'Sample code from two.js.'; console.log(sample); var sample = 'Sample code from three.js.'; console.log(sample);
还有更多...
concat任务为我们提供了几个选项,允许我们使用自定义分隔符连接文件,在连接之前去除横幅,向连接结果添加横幅和页脚,以及在连接之前处理源文件的内容。
使用自定义分隔符连接文件
通常,在连接的文件之间有一个明显的分隔符会更受欢迎,这样在需要审查连接结果的内容时更容易区分它们。
默认情况下,concat任务将仅使用换行符分隔连接的文件,但可以通过separator选项提供自定义分隔符,如下例所示:
concat: {
sample: {
options: {
separator: '\n\n/* Next file */\n\n'
},
files: {
'all.js': ['one.js', 'two.js', 'three.js']
}
}
}
使用前面的示例代码运行任务应该会产生一个内容类似于以下内容的all.js文件:
var sample = 'Sample code from one.js.\n';
console.log(sample);
/* Next file */
var sample = 'Sample code from two.js.\n';
console.log(sample);
/* Next file */
var sample = 'Sample code from three.js.\n';
console.log(sample);
在连接之前去除横幅
在连接之前从 JavaScript 源文件中去除横幅的需求相当常见,因为它们通常占用相当多的额外存储空间,而且正如每个 Web 开发者都知道的,每一比特都很重要。
要自动从 JavaScript 源文件中删除横幅,我们可以按照以下示例使用 stripBanners 选项:
concat: {
sample: {
options: {
stripBanners: true
},
files: {
'all.js': ['one.js', 'two.js', 'three.js']
}
}
}
向合并结果添加横幅和页脚
一旦生成了合并的结果,添加一个横幅或页脚以提供一些额外信息通常很有用。以下示例正是通过使用 banner 和 footer 选项来做到这一点的:
concat: {
sample: {
options: {
banner: '/* The combination of three files */\n',
footer: '\n/* No more files to be combined */'
},
files: {
'all.js': ['one.js', 'two.js', 'three.js']
}
}
}
使用前面的示例代码运行任务应该会生成一个包含以下内容的 all.js 文件:
/* The combination of three files */
var sample = 'Sample code from one.js.\n';
console.log(sample);
var sample = 'Sample code from two.js.\n';
console.log(sample);
var sample = 'Sample code from three.js.\n';
console.log(sample);
/* No more files to be combined */
在合并之前处理源文件的内容
如果我们想修改合并的文件内容,concat 任务提供了在合并之前使用代码进行修改的选项。这可以通过向 process 选项提供一个函数来实现,该函数接收文件内容,修改它,并返回它。
以下示例利用此功能在合并的每个文件上方创建一个横幅,指示其文件名:
concat: {
sample: {
options: {
process: function (content, filename) {
var banner = '/* ' + filename + ' */\n';
return banner + content;
}
},
files: {
'all.js': ['one.js', 'two.js', 'three.js']
}
}
}
使用前面的示例代码运行任务应该会生成一个包含以下内容的 all.js 文件:
/* one.js */
var sample = 'Sample code from one.js.\n';
console.log(sample);
/* two.js */
var sample = 'Sample code from two.js.\n';
console.log(sample);
/* three.js */
var sample = 'Sample code from three.js.\n';
console.log(sample);
获取单个 URL
如果您正在处理基于 Web 的项目,您可能需要在某个时候下载一些内容。下载文件是一个相当简单的操作,但自动化它可以从长远来看为您节省大量时间。
我们将使用相对流行的 curl (2.0.2) 插件从互联网下载资源。该插件提供了 curl 任务以下载单个文件。
准备工作
在本例中,我们将使用我们在 第一章 中创建的基本项目结构,即 在项目中安装 Grunt 菜谱,使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导我们使用 OpenWeatherMap API 下载伦敦市的当前天气并将其保存到 london.json 文件中。
-
我们将按照 安装插件 菜谱中提供的说明安装包含
curl插件的包,该菜谱位于 第一章,使用 Grunt 入门。 -
现在,我们可以添加以下配置以将伦敦当前的天气信息下载到
london.json文件中:curl: { london: { src: 'http://api.openweathermap.org/' + 'data/2.5/weather?q=London', dest: 'london.json' } } -
为了测试我们的设置,我们可以运行
grunt curl命令,它应该会提供类似于以下输出的结果:Running "curl:london" (curl) task File "london.json" created. -
如果我们现在查看项目目录,我们应该看到下载的天气信息已保存到
london.json文件中。
还有更多...
如果我们需要在下载内容时获得更多灵活性,curl 任务允许我们使用 request 包提供的 request HTTP 客户端实用工具来实现。可以在 src 配置中提供用于 request 实用工具的相同选项。
小贴士
你可以在以下网址了解更多关于 request HTTP 客户端实用程序的信息:
以下示例使用此功能为请求的查询字符串提供一个对象,而不是将其包含在 URL 字符串中:
curl: {
london: {
src: {
url: 'http://api.openweathermap.org/data/2.5/weather',
qs: {q:'London'}
},
dest: 'london.json'
}
}
获取多个 URL
如果你正在为你的项目自动化资源下载,你可能希望将多个资源下载到同一个目录中,并且很可能是从同一个网站下载。
我们将使用相对流行的 curl (2.0.2) 插件从互联网下载多个资源。它提供了一个专门的 curl-dir 任务来下载多个资源。
准备工作
在这个例子中,我们将使用我们在 第一章 中 在项目中安装 Grunt 菜单创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们将 Grunt 和 Node.js 的标志都下载到 logos 目录中。
-
我们将按照 第一章 中 安装插件 菜单提供的说明来安装包含
curl插件的包,使用 Grunt 开始。 -
现在,我们可以添加以下
curl-dir任务配置,它将下载两个标志到logos目录中:'curl-dir': { logos: { src: [ 'http://nodejs.org/images/logo.png', 'http://www.gruntjs.com/img/grunt-logo.png' ], dest: 'logos' } } -
要测试任务,我们可以使用
grunt curl-dir命令来运行它,它应该提供类似于以下输出的结果:Running "curl-dir:logo" (curl-dir) task Files "logos/logo.png", "logos/grunt-logo.png" created. -
现在我们应该在项目路径中有一个名为
logos的目录,其中包含 Grunt 和 Node.js 的标志。
更多内容...
curl-dir 任务为我们提供了几个选项,允许我们下载类似的 URL,将下载的文件保存到修改后的文件名中,并使用特殊请求选项下载文件。
下载类似的 URL
同时,我们可能想要下载多个非常相似的 URL。在这种情况下,我们可以利用 curl-dir 任务提供的花括号扩展支持。
以下配置示例说明了我们如何下载伦敦、巴黎和东京的天气信息,而无需重复每个 URL 的起始部分:
'curl-dir': {
weather: {
src: 'http://api.openweathermap.org/'
+ 'data/2.5/weather?q={London,Paris,Tokyo}',
dest: 'weather'
}
}
将下载的文件保存到修改后的文件名中
保存下载的资源到不同于其 URL 中包含的文件名也是相当常见的。curl-dir 任务提供的 router 配置允许我们指定一个函数,该函数接收我们下载的 URL 并返回资源应该保存到的文件名。
以下配置示例说明了我们如何下载三个城市的天气信息,并将每个结果保存到仅包含城市名称和 .json 文件扩展名的文件中:
'curl-dir': {
weather: {
router: function (url) {
var city = url.slice(url.indexOf('=') + 1, url.length);
return city + '.json';
},
src: 'http://api.openweathermap.org/'
+ 'data/2.5/weather?q={London,Paris,Tokyo}',
dest: 'weather'
}
}
使用特殊请求选项下载文件
如果我们需要在下载内容请求中增加更多的灵活性,curl-dir任务允许我们通过使用request包提供的request HTTP 客户端工具来实现这一点。可以为request工具提供的相同选项在src配置中也可以提供。
以下配置示例通过将查询参数作为对象而不是附加到 URL 的字符串来利用此功能,同时也展示了如何使用之前讨论过的router配置来设置此类环境:
'curl-dir': {
weather: {
router: function (src) {
return src.qs.q + '.json';
},
src: [{
url: 'http://api.openweathermap.org/data/2.5/weather',
qs: {q:'London'}
}, {
url: 'http://api.openweathermap.org/data/2.5/weather',
qs: {q:'Paris'}
}],
dest: 'weather'
}
}
第三章。模板引擎
在本章中,我们将介绍以下菜谱:
-
渲染 Jade 模板
-
在 Jade 模板中使用数据
-
在 Jade 模板中使用自定义过滤器
-
编译 Jade 模板
-
编译 Handlebars 模板
-
编译 Underscore 模板
-
在 Handlebars 模板中使用部分模板
-
在 AMD 模块中包装 Jade 模板
-
在 AMD 模块中包装 Handlebars 模板
-
在 AMD 模块中包装 Underscore 模板
-
在 CommonJS 模块中包装 Handlebars 模板
-
在编译前修改 Jade 模板
-
在编译前修改 Handlebars 模板
-
在编译前修改 Underscore 模板
简介
通过组合逻辑和数据生成内容提出了一系列独特的问题,模板引擎专门设计来解决这些问题。在本章中,我们将主要关注使用各种模板引擎生成 HTML,尽管其中一些设计成允许生成几乎任何类型的可读文件格式。
尽管许多功能丰富的 JavaScript 社区中的 Web 应用程序框架都提供了模板的按需编译、渲染和缓存,但使用 Grunt 做到这一点提供了构建高度优化和专用解决方案所需的灵活性。模板可以直接渲染到 HTML 或编译为 JST,可以直接使用而无需任何额外处理。这些模板的产物也可以打包,以便在几乎任何环境中使用。
在撰写本文时,有许多优秀的模板引擎可供选择,其中大多数都通过插件支持 Grunt,但本章我们将重点关注 Grunt 社区最广泛使用的那些。
渲染 Jade 模板
Jade 模板引擎允许我们使用其最小化且熟悉的语法轻松构建和维护 HTML 模板。在本菜谱中,我们将使用 contrib-jade (0.12.0) 插件来编译一个渲染简单 HTML 页面的模板。
准备工作
在本例中,我们将使用我们在 第一章 中创建的基本项目结构,即 在项目中安装 Grunt 菜谱,开始使用 Grunt。如果您还不熟悉其内容,请务必参考。
如何做到这一点...
以下步骤将引导我们创建一个简单的 Jade 模板并将其渲染为 HTML 文件。
-
我们将首先按照 第一章 中提供的 安装插件 菜谱说明安装包含
contrib-jade插件的包,开始使用 Grunt。 -
让我们在项目目录中创建一个简单的 Jade 模板,命名为
index.jade,并添加以下内容:doctype html html head title Index body h1 Index -
现在,我们可以在配置中添加以下
jade任务,它将编译我们刚刚创建的index.jade文件到项目目录中的index.html文件:jade: { sample: { options: { pretty: true }, src: 'index.jade', dest: 'index.html' } }小贴士
在这个例子中,我们将
pretty选项设置为true,以便从模板生成的 HTML 更易于阅读。当项目仍在开发阶段时,这通常是首选的,但在生产环境中通常不使用此选项,因为启用此选项会增加生成文件的大小。 -
最后,我们可以使用
grunt jade命令运行任务,它应该产生类似于以下内容的输出:Running "jade:sample" (jade) task File index.html created. -
如果我们现在查看我们的项目目录,我们应该看到包含以下内容的新的
index.html文件:<!DOCTYPE html> <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> </body> </html>
在 Jade 模板中使用数据
一旦我们有了 Jade 模板,我们就可以使用它以各种数据渲染相同的页面结构。在这个食谱中,我们将使用 contrib-jade (0.12.0) 插件与 data 选项结合使用,以发送应在模板渲染中使用的数据。
准备工作
在这个例子中,我们将使用我们在本章中 渲染 Jade 模板 食谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们在渲染模板时提供数据,并修改我们的 index.jade 模板以使用提供的数据。
-
首先,我们将修改我们的
index.jade模板,以便使用其上下文提供的变量:doctype html html head title= title body h1= title p= body -
现在,我们可以为
title和body变量提供值,这些值将由模板使用。这是通过在配置中添加的data选项中指定它们来完成的:jade: { sample: { options: { pretty: true, data: { title: 'Most Amazing Site', body: 'Disappointing mundane content.' } }, src: 'index.jade', dest: 'index.html' } } -
最后,我们可以使用
grunt jade命令运行任务,它应该产生类似于以下内容的输出:Running "jade:sample" (jade) task File index.html created. -
如果我们现在查看运行任务生成的
index.html文件,我们会发现模板中指示的变量已被data选项中指定的值所替换:<!DOCTYPE html> <html> <head> <title>Most Amazing Site</title> </head> <body> <h1>Most Amazing Site</h1> <p>Disappointing mundane content.</p> </body> </html>
还有更多...
通常来说,将模板中使用的数据硬编码在 Grunt 配置中并不是一个好主意,而是应该从外部数据源导入。
以下步骤将指导我们创建外部数据源并修改我们的配置,以便在渲染模板时使用的数据从中加载。
-
首先,我们在项目目录中创建一个名为
data.json的文件,其中包含我们希望在模板渲染中使用的所有数据:{ "title": "Most Amazing Site", "body": "Disappointing mundane content." } -
然后,我们可以修改我们的
jade任务配置,通过使用grunt.file.readJSON方法从外部源导入数据:jade: { sample: { options: { pretty: true, data: grunt.file.readJSON('data.json') }, src: 'index.jade', dest: 'index.html' } } -
如果我们现在使用
grunt jade命令运行任务,我们应该得到与主食谱中完全相同的结果。
在 Jade 模板中使用自定义过滤器
Jade 模板引擎提供的过滤器使我们能够指定在处理模板中的特定块时应使用的方法。这通常用于以 Jade 语言本身之外的其他格式编写的块内容。例如,Jade 库提供的coffee和markdown过滤器可以将CoffeeScript代码渲染成 JavaScript,将Markdown内容渲染成 HTML。
在这个菜谱中,我们将使用contrib-jade (0.12.0)插件及其filters选项,使我们能够在模板中访问一个名为link的自定义过滤器。此过滤器将使用一个简单的算法来查找 URL 并将它们包裹在锚标签中,从而将它们转换为链接。
准备工作
在这个例子中,我们将使用我们在本章渲染 Jade 模板菜谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们添加link自定义过滤器并修改我们的index.jade模板以使用它。
-
首先,我们将通过使用之前配置的
jade任务中的filters选项来添加link自定义过滤器到我们的配置中:jade: { sample: { options: { pretty: true, filters: { link: function (block) { var url_re = /(http:\/\/[^\s]*)/; var link_replace = '<a href="$1">$1</a>'; return block.replace(url_re, link_replace); } } }, src: 'index.jade', dest: 'index.html' } } -
现在,我们可以修改我们的
index.jade模板的内容,以使用我们的link自定义过滤器:doctype html html head title Index body h1 Index p :link Be sure to check out http://gruntjs.com/ for more information. -
最后,我们可以使用
grunt jade命令运行任务,它应该产生类似于以下内容的输出:Running "jade:sample" (jade) task File index.html created. -
如果我们现在查看由运行任务生成的
index.html文件,我们会发现它已经用锚标签包围了我们提供给link过滤器的块内的链接:<!DOCTYPE html> <html> <head> <title>Index</title> </head> <body> <h1>Index</h1> <p> Be sure to check out <a href="http://gruntjs.com/">http://gruntjs.com/</a> for more information. </p> </body> </html>
编译 Jade 模板
当构建一个需要在尽可能短的时间内在前端渲染 HTML 模板的 Web 应用程序时,编译模板变得至关重要。Jade 模板引擎允许我们将模板编译成用于前端的JavaScript 模板(JSTs)。
在这个菜谱中,我们将使用contrib-jade (0.12.0)插件来编译一个渲染简约博客的模板。
准备工作
在这个例子中,我们将使用我们在第一章中在项目中安装 Grunt菜谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们创建一个简单的 Jade 模板并将其编译成包含在其他文件中的 JST。
-
我们将按照第一章中安装插件菜谱中提供的说明来安装包含
contrib-jade插件的包,该菜谱位于开始使用 Grunt。 -
让我们在项目目录中创建一个名为
blog.jade的简单 Jade 模板文件,并给它以下内容:.blog h1= title each post in posts .post h2= post.title p= post.body -
现在,我们可以将以下
jade任务添加到我们的配置中,它将blog.jade模板编译成包含在我们项目目录的templates.js文件中的 JST:jade: { blog: { options: { client: true }, src: 'blog.jade', dest: 'templates.js' } } -
最后,我们可以使用
grunt jade命令来运行任务,它应该会产生类似于以下内容的输出:Running "jade:blog" (jade) task File templates.js created. -
我们现在应该在项目目录中有一个名为
templates.js的新文件,其中包含我们编译模板的 JST 代码。
它是如何工作的...
以下步骤展示了编译模板的使用示例,并展示了一个渲染结果的示例。
-
首先,您需要将 Jade 运行时库包含到您希望使用编译模板的应用程序或页面中。
小贴士
在撰写本文时,Jade 运行时库位于官方 Jade 仓库的根目录,可以通过以下网址下载:
-
然后,您需要将
jade任务生成的templates.js文件包含到应用程序或页面中,以便使包含模板的JST全局变量可用。 -
以下示例代码将渲染编译后的模板,使用一些样本数据并将结果存储在
result变量中:var result = JST'blog'; -
result变量现在应该包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
还有更多...
jade 任务在基本模板编译的基础上提供了一些有用的选项,允许我们指定编译模板的命名空间,指示模板名称应该如何从文件名中派生,以及带有调试支持的模板编译。
指定编译模板的命名空间
默认情况下,编译后的模板存储在 JST 命名空间中,但可以通过使用 namespace 选项将其更改为任何我们喜欢的名称。在以下示例中,我们配置任务将模板存储在 Templates 命名空间中:
jade: {
blog: {
options: {
client: true,
namespace: 'Templates'
},
src: 'blog.jade',
dest: 'templates.js'
}
}
指示模板名称应该如何从文件名中派生
可以使用 processName 选项来指示模板在命名空间中存储的名称应该如何从文件名中派生。jade 任务的默认行为是使用文件扩展名之前的所有内容。在以下示例中,我们指示整个文件名应该全部大写:
jade: {
blog: {
options: {
client: true,
processName: function (filename) {
return filename.toUpperCase();
}
},
src: 'blog.jade',
dest: 'templates.js'
}
}
带有调试支持的模板编译
Jade 模板引擎为编译后的模板提供了额外的调试支持,可以通过 compileDebug 选项启用,如下例所示:
jade: {
blog: {
options: {
client: true,
compileDebug: true
},
src: 'blog.jade',
dest: 'templates.js'
}
}
编译 Handlebars 模板
Handlebars 模板引擎简化了任何标记或其他可读文件的构建和维护。它熟悉的语法、可扩展性和无逻辑的方法提供了一个全面的模板构建体验。该引擎主要关注将模板编译成在 Web 应用程序前端广泛使用的 JST。
在这个菜谱中,我们将使用 contrib-handlebars (0.8.0) 插件来编译一个渲染简约博客的模板。
准备工作
在这个例子中,我们将使用我们在 第一章 中创建的基本项目结构,即 在项目中安装 Grunt 菜谱。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们创建一个简单的 Handlebars 模板,并设置 Grunt 以将其编译到另一个文件中包含的 JST。
-
我们将按照 第一章 中提供的 安装插件 菜谱中的说明安装包含
contrib-handlebars插件的包。 -
让我们在项目目录中创建一个简单的 Handlebars 模板文件,命名为
blog.hbs,并给它以下内容:<div class="blog"> <h1>{{title}}</h1> {{#posts}} <h2>{{title}}</h2> <p>{{body}}</p> {{/posts}} </div> -
现在,我们可以在配置中添加以下
handlebars任务,该任务将post.hbs模板编译到templates.js文件中的 JST 内:handlebars: { blog: { src: 'blog.hbs', dest: 'templates.js' } } -
最后,我们可以使用
grunt handlebars命令运行任务,它应该产生类似于以下内容的输出:Running "handlebars:blog" (handlebars) task >> 1 file created. -
现在我们应该在项目目录中有一个名为
templates.js的新文件,其中包含编译模板的 JST 代码。
它是如何工作的...
以下步骤展示了编译模板的使用方法,并显示了渲染结果的示例。
-
首先,您需要将 Handlebars 运行时库包含到您想要使用编译模板的应用程序或页面中。
小贴士
在撰写本文时,Handlebars 运行时库可以从以下 URL 下载:
builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js -
然后,您需要将由
handlebars任务生成的templates.js文件包含到应用程序或页面中,以创建包含模板的JST全局变量。 -
以下示例代码将使用一些样本数据渲染编译后的模板,并将结果存储在
result变量中:var result = JST'blog.hbs'; -
result变量现在应该包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
还有更多...
handlebars 任务在基本模板编译的同时提供了一些有用的选项,允许我们指定编译模板的命名空间,并指示模板名称应该如何从它们的文件名中派生。
指定编译模板的命名空间
默认情况下,编译后的模板存储在 JST 命名空间中,但可以通过使用 namespace 选项将其更改为任何我们喜欢的名称。在以下示例中,我们配置任务以将模板存储在 Templates 命名空间中:
handlebars: {
blog: {
options: {
namespace: 'Templates'
},
src: 'blog.hbs',
dest: 'templates.js'
}
}
指示模板名称应该如何从文件名中派生
processName 选项可用于指示模板在命名空间中存储的名称应如何从其文件名派生。handlebars 任务的默认行为是使用整个文件名。在以下示例中,我们指示它仍然使用文件名,但以大写字母形式:
handlebars: {
blog: {
options: {
processName: function (filename) {
return filename.toUpperCase();
}
},
src: 'blog.hbs',
dest: 'templates.js'
}
}
编译 Underscore 模板
除了提供的许多实用工具外,Underscore 库还包括一个简单、快速且灵活的模板引擎。这些模板通常编译为 JST 以在 Web 应用程序的前端使用。
在这个菜谱中,我们将使用 contrib-jst (0.6.0) 插件来编译一个渲染简约博客的模板。
准备工作
在这个示例中,我们将使用我们在 第一章 中 在项目中安装 Grunt 菜谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们创建一个简单的 Underscore 模板并将其编译到另一个文件中的 JST 中。
-
我们将按照 第一章 中 安装插件 菜谱提供的说明,安装包含
contrib-jst插件的包。 -
让我们在项目目录中创建一个简单的 Underscore 模板文件,命名为
blog.html,并给它以下内容:<div class="blog"> <h1><%= title %></h1> <% _.each(posts, function (post) { %> <div class="post"> <h2><%= post.title %></h2> <p><%= post.body %></p> </div> <% }) %> </div> -
现在,我们可以将以下
jst任务添加到我们的配置中,该任务将blog.html模板编译到我们项目目录中的templates.js文件中:jst: { blog: { src: 'blog.html', dest: 'templates.js' } } -
最后,我们可以使用
grunt jst命令运行任务,它应该产生类似于以下内容的输出:Running "jst:blog" (handlebars) task >> 1 file created. -
现在,我们应该在我们的项目目录中有一个名为
templates.js的新文件,其中包含我们编译模板的 JST 代码。
它是如何工作的...
以下步骤演示了编译模板的使用,并显示了一个渲染结果的示例。
-
首先,您需要在希望使用编译模板的应用程序或页面中包含 Underscore 运行时库。
提示
在撰写本文时,可以从以下 URL 下载 Underscore 运行时库:
-
然后,您需要将
jst任务生成的templates.js文件包含到应用程序或页面中,以便使包含模板的JST全局变量可用。 -
以下示例代码将渲染编译后的模板,使用一些示例数据并将结果存储在
result变量中:var result = JST'blog.html'; -
result变量现在应包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
还有更多...
jst 任务在基本模板编译的同时提供了一些有用的选项,允许我们指定编译模板的命名空间并指示模板名称应如何从其文件名派生。
指定编译模板的命名空间
默认情况下,编译后的模板存储在 JST 命名空间中,但可以通过使用 namespace 选项将其更改为我们喜欢的任何内容。在以下示例中,我们配置任务将模板存储在 Templates 命名空间中:
jst: {
blog: {
options: {
namespace: 'Templates'
},
src: 'blog.html',
dest: 'templates.js'
}
}
指示模板名称应如何从文件名派生
可以使用 processName 选项来指示模板在命名空间中存储时应使用的名称应如何从其文件名派生。jst 任务的默认行为是使用整个文件名。在以下示例中,我们指示它仍然使用文件名,但使用大写字母:
jst: {
blog: {
options: {
processName: function (filename) {
return filename.toUpperCase();
}
},
src: 'blog.html',
dest: 'templates.js'
}
}
在 Handlebars 模板中使用部分
Handlebars 模板引擎提供的部分系统允许我们在不同的上下文中重用较小的模板代码片段。每当你在模板的不同位置看到重复的模式时,这可能是一个使用部分的好机会。
在本菜谱中,我们将使用 contrib-handlebars (0.8.0) 插件及其部分模板加载功能,在博客的两个不同部分中渲染具有类似结构的帖子。
准备工作
在本例中,我们将使用本章中 编译 Handlebars 模板 菜单中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们创建一个帖子部分模板并修改我们的博客模板,以便渲染新的和旧的章节。
-
默认情况下,
handlebars任务将以下划线开头的模板识别为部分模板并加载。让我们在我们的项目目录中创建一个名为_post.hbs的文件,它将包含我们的部分帖子模板:<div class="post"> <h3>{{title}}</h3> <p>{{body}}</p> </div> -
现在,我们将通过将新部分模板的文件名添加到任务应包含的模板列表中来修改任务的配置:
handlebars: { sample: { src: ['blog.hbs', '_post.hbs'], dest: 'templates.js' } } -
一旦设置好部分模板以便包含,我们就可以修改我们的
blog.hbs模板以使用它。让我们修改它以渲染新的和旧的章节:<div class="blog"> <h1>{{title}}</h1> <h2>New Posts</h2> {{#newPosts}} {{>post}} {{/newPosts}} <h2>Old Posts</h2> {{#oldPosts}} {{>post}} {{/oldPosts}} </div> -
最后,我们可以使用
grunt handlebars命令运行任务,它应该产生类似于以下内容的输出:Running "handlebars:blog" (handlebars) task >> 1 file created. -
现在,我们应该在我们的项目目录中有一个修改后的
templates.js文件版本,其中包含我们最新博客模板的 JST 代码。
它是如何工作的...
以下步骤演示了编译模板的使用并显示了一个渲染结果的示例。
-
首先,你需要在希望使用编译模板的应用程序或页面中包含 Handlebars 运行时库。
小贴士
在撰写本文时,Handlebars 运行时库可以从以下 URL 下载:
builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js -
然后,你需要将
handlebars任务生成的templates.js文件包含在应用程序或页面中,以便使包含模板的JST全局变量可用。 -
以下示例代码将渲染编译后的模板,使用一些示例数据并将结果存储在
result变量中:var result = JST'blog.hbs'; -
result变量现在应包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>New Posts</h2> <div class="post"> <h3>Brand Spanking</h3> <p>Still has that new post smell.</p> </div> <div class="post"> <h3>Shiny Post</h3> <p>Just got off the floor.</p> </div> <h2>Old Posts</h2> <div class="post"> <h3>Old News</h3> <p>Not that relevant anymore.</p> </div> <div class="post"> <h3>Way Back When</h3> <p>My very first post.</p> </div> </div>
还有更多...
handlebars任务提供了一些有用的选项,与部分模板的加载结合使用,允许我们在命名空间中使部分模板可用,指示部分模板应该如何识别,以及指示部分模板名称应该如何推导。
在命名空间中使部分模板可用
如果我们想在代码中直接使用部分模板,而不仅仅是其他模板中,我们可以通过使用partialsUseNamespace选项,就像以下示例中那样,在命名空间中使它们与其他模板一起可用:
handlebars: {
blog: {
options: {
partialsUseNamespace: true
},
src: ['blog.hbs', '_post.hbs'],
dest: 'templates.js'
}
}
指示如何识别部分模板
我们可能会达到想要更改部分模板识别方式的地步。这可以通过组合使用partialRegex和partialsPathRegex选项来完成。以下示例表明,所有在partials目录中找到的模板都应该被加载为部分模板:
handlebars: {
blog: {
options: {
partialRegex: /.*/,
partialsPathRegex: /partials\//
},
src: ['blog.hbs', 'partials/post.hbs'],
dest: 'templates.js'
}
}
指示如何推导部分模板的名称
默认情况下,handlebars任务会从部分文件名中移除前导下划线和文件扩展名,以确定其名称。以下示例使用processPartialName选项来指示在使用之前应将整个文件名转换为大写:
handlebars: {
blog: {
options: {
processPartialName: function (filename) {
return filename.toUpperCase();
}
},
src: ['blog.hbs', '_post.hbs'],
dest: 'templates.js'
}
}
将 Jade 模板包裹在 AMD 模块中
在这个食谱中,我们将使用contrib-jade (0.12.0)插件及其amd选项来将我们的编译后的博客模板包裹在一个异步模块定义(AMD)模块中。
准备工作
在这个例子中,我们将使用本章中“编译 Jade 模板”食谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何做到这一点...
以下步骤将引导我们修改配置,以便它将编译后的模板包裹在一个 AMD 模块中。
-
首先,我们将通过添加
amd选项来修改配置,以指示编译后的模板应该被包裹在一个 AMD 模块中:jade: { blog: { options: { amd: true, client: true }, src: 'blog.jade', dest: 'templates.js' } } -
使用
namespace选项,我们现在也可以指定不再希望编译后的模板包含在JST命名空间中,因为它们现在将包含在自己的模块中:jade: { blog: { options: { amd: true, client: true, namespace: false }, src: 'blog.jade', dest: 'templates.js' } } -
现在,我们还可以将模板编译为不同的文件名,因为每个模板现在将代表其自己的文件:
jade: { blog: { options: { amd: true, client: true, namespace: false }, src: 'blog.jade', dest: 'blog.js' } } -
最后,我们可以使用
grunt jade命令来运行任务,它应该产生类似于以下内容的输出:Running "jade:blog" (jade) task File blog.js created. -
现在应该在项目目录中有一个名为
blog.js的文件,该文件包含了一个用 AMD 模块包裹的编译后的模板。
它是如何工作的...
以下步骤展示了编译后包裹的模板的使用方法,并显示了渲染结果的示例。
-
首先,你需要确保你现有的 AMD 框架能够访问名为
jade的 Jade 运行时库。这可能需要修改 AMD 框架的配置。小贴士
在撰写本文时,Jade 运行时库可在官方 Jade 仓库的根目录下找到,并且可以从以下 URL 下载:
-
然后,你需要确保由
jade任务生成的blog.js文件能够被 AMD 框架访问,无论你更喜欢哪个名称或路径。 -
以下示例代码将获取编译后的模板作为依赖项,使用一些示例数据渲染编译后的模板,然后将结果存储在
result变量中:define(['templates/blog'], function(blog) { var result = blog({ title: 'My Awesome Blog', posts: [{ title: 'First Post', body: 'My very first post.' }, { title: 'Second Post', body: 'This is getting old.' }] }); }); -
result变量现在应该包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
在 AMD 模块中包裹 Handlebars 模板
在这个菜谱中,我们将使用 contrib-handlebars (0.8.0) 插件及其 amd 选项来将编译后的博客模板包裹在 AMD 模块中。
准备工作
在这个例子中,我们将使用本章中 编译 Handlebars 模板 菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何做到...
以下步骤将引导我们修改配置,以便将编译后的模板包裹在 AMD 模块中。
-
首先,我们将通过添加
amd选项来修改配置,以指示编译后的模板应该被包裹在 AMD 模块中:handlebars: { blog: { options: { amd: true }, src: 'blog.hbs', dest: 'templates.js' } } -
使用
namespace选项,我们现在也可以指定我们不再希望编译后的模板包含在JST命名空间中,因为它们现在将包含在自己的模块中:handlebars: { blog: { options: { amd: true, namespace: false }, src: 'blog.hbs', dest: 'templates.js' } } -
我们现在也可以将模板编译到不同的文件名中,因为每个模板现在都将由其自己的文件表示:
handlebars: { blog: { options: { amd: true, namespace: false }, src: 'blog.hbs', dest: 'blog.js' } } -
最后,我们可以使用
grunt handlebars命令来运行任务,它应该产生类似于以下内容的输出:Running "handlebars:blog" (handlebars) task >> 1 file created. -
现在应该在项目目录中有一个名为
blog.js的文件,该文件包含了一个用 AMD 模块包裹的编译后的模板。
它是如何工作的...
以下步骤展示了编译后包裹的模板的使用方法,并显示了渲染结果的示例。
-
首先,你需要确保你现有的 AMD 框架能够访问名为
handlebars的 Handlebars 运行时库。这可能需要修改 AMD 框架的配置。小贴士
在撰写本文时,Handlebars 运行时库可以从以下网址下载:
builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js在本提示中提到的运行时库的情况下,您将不得不使用垫片(shim),因为该库没有为与 AMD 模块一起使用而准备。您可以通过以下网址了解更多关于使用 RequireJS 框架进行垫片的信息:
-
然后,您需要确保由
handlebars任务生成的blog.js文件在您选择的任何名称或路径下都可以被 AMD 框架访问。 -
以下示例代码将获取编译模板作为依赖项,使用一些示例数据渲染编译模板,然后将结果存储在
result变量中:define(['templates/blog'], function(blog) { var result = blog({ title: 'My Awesome Blog', posts: [{ title: 'First Post', body: 'My very first post.' }, { title: 'Second Post', body: 'This is getting old.' }] }); }); -
result变量现在应包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
在 AMD 模块中包装 Underscore 模板
在本配方中,我们将使用 contrib-jst (0.6.0) 插件及其 amd 选项来将编译后的博客模板包装在一个 AMD 模块中。
准备工作
在本例中,我们将使用本章“编译 Underscore 模板”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们修改配置,以便将我们的编译模板包装在 AMD 模块中。
-
首先,我们将通过添加
amd选项来修改配置,以指示编译模板应包装在 AMD 模块中:jst: { blog: { options: { amd: true }, src: 'blog.html', dest: 'templates.js' } } -
使用
namespace选项,我们现在还可以指定我们不再希望编译模板包含在JST命名空间中,因为它们现在将包含在其自己的模块中:jst: { blog: { options: { amd: true, namespace: false }, src: 'blog.html', dest: 'templates.js' } } -
现在,我们还可以将模板编译为不同的文件名,因为每个模板现在都将位于其自己的文件中:
jst: { blog: { options: { amd: true, namespace: false }, src: 'blog.html', dest: 'blog.js' } } -
最后,我们可以使用
grunt jst命令运行任务,这应该会产生类似于以下内容的输出:Running "jst:blog" (jst) task File blog.js created. -
现在应在项目目录中有一个名为
blog.js的文件,其中包含包装在 AMD 模块中的编译模板。
工作原理...
以下步骤演示了编译和包装模板的使用,并显示了一个渲染结果的示例。
-
首先,您需要确保您放置的 AMD 框架在启动时加载 Underscore 库,因为此插件创建的包装器不将其作为依赖项引用。
提示
在撰写本文时,Underscore 运行时库可以从以下网址下载:
underscorejs.org/underscore-min.js您可以在以下网址了解更多关于在 RequireJS 框架中指定启动依赖项的信息:
-
然后,你需要确保由
jst任务生成的blog.js文件可以在你喜欢的任何名称或路径下被 AMD 框架访问。 -
以下示例代码将获取编译后的模板作为依赖项,使用一些示例数据渲染编译后的模板,然后将结果存储在
result变量中:define(['templates/blog'], function(blog) { var result = blog({ title: 'My Awesome Blog', posts: [{ title: 'First Post', body: 'My very first post.' }, { title: 'Second Post', body: 'This is getting old.' }] }); }); -
result变量现在应包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
在 CommonJS 模块中包装 Handlebars 模板
在这个菜谱中,我们将使用 contrib-handlebars (0.8.0) 插件将编译后的博客模板包装在一个 CommonJS 模块中。
准备工作
在这个例子中,我们将使用本章中 编译 Handlebars 模板 菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们修改配置,以便将编译后的模板包装在 CommonJS 模块中。
-
首先,我们将通过添加
amd选项来修改配置,表示编译后的模板应该被包装在 CommonJS 模块中。handlebars: { blog: { options: { commonjs: true }, src: 'blog.hbs', dest: 'templates.js' } } -
使用
namespace选项,我们现在也可以指定不再希望编译后的模板包含在JST命名空间中,因为它们现在将包含在一个模块中:handlebars: { blog: { options: { commonjs: true, namespace: false }, src: 'blog.hbs', dest: 'templates.js' } } -
最后,我们可以使用
grunt handlebars命令运行任务,它应该产生类似以下输出:Running "handlebars:blog" (handlebars) task >> 1 file created. -
现在项目目录中应该有一个
templates.js文件,其中包含包装在 CommonJS 模块中的编译后的模板。
他是如何工作的...
以下步骤展示了编译和包装模板的使用方法,并显示了渲染结果的示例。
-
首先,我们必须确保 Handlebars 库已安装在本地的 CommonJS 支持环境中。以 Node.js 为例,模块必须使用
npm install handlebars命令安装。 -
接下来,我们将通过使用类似以下代码的方式,使代码片段中的模板可用,我们将导入模板:
var Handlebars = require('handlebars'); -
然后,我们需要将模板导入到我们的代码中,并通过使用类似以下代码的方式将其传递给 Handlebars 库:
var templates = require('./templates')(Handlebars);小贴士
这段代码假设生成的
templates.js文件与当前正在工作的代码文件位于同一路径上。 -
现在我们已经导入了模板,我们可以使用它们。以下示例渲染了
blog.hbs模板,并将结果存储在result变量中:var result = templates'blog.hbs'; -
result变量现在应包含以下 HTML:<div class="blog"> <h1>My Awesome Blog</h1> <h2>First Post</h2> <p>My very first post.</p> <h2>Second Post</h2> <p>This is getting old.</p> </div>
在编译前修改 Jade 模板
有时候,你可能想在编译或渲染模板之前修改模板的内容。这通常是在你想要从模板中移除过多的空白空间,或者如果你想移除或替换你无法控制的模板部分时所需的。
在这个菜谱中,我们将利用 contrib-jade (0.12.0) 插件及其 jade 任务提供的 processContent 选项,在将模板渲染为 HTML 之前从我们的模板中移除空白字符。
准备工作
在这个示例中,我们将使用本章中 编译 Jade 模板 菜谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们修改配置,以便在将模板渲染为 HTML 之前从模板中移除过多的空白字符。
-
首先,我们可以向
processContent选项提供一个空函数,该函数接收模板的内容并将其传递而不进行修改:jade: { sample: { options: { processContent: function (content) { return content; } }, src: 'index.jade', dest: 'index.html' } } -
然后,我们可以修改提供给
processContent的函数的代码,以便从模板的内容中移除所有尾随空白字符:processContent: function (content) { content = content.replace(/[\x20\t]+$/mg, ''); content = content.replace(/[\r\n]*$/, '\n'); return content; } -
如果我们现在使用
grunt jade命令运行任务,我们将看到以下类似的输出:Running "jade:sample" (jade) task File index.html created. -
在
index.html文件中显示的渲染结果现在不应显示任何原始模板中存在的尾随空白字符的迹象。
在编译 Handlebars 模板之前进行修改
虽然这种情况并不常见,但可能会有这样的时候,您希望在编译或渲染模板之前修改模板的内容。这通常是在您希望从模板中移除过多的空白字符,或者如果您想移除或替换您无法控制的模板的某些部分时所需的。
在这个菜谱中,我们将利用 contrib-handlebars (0.8.0) 插件及其 handlebars 任务提供的 processContent 选项,在编译模板之前从我们的模板中移除空白字符。
准备工作
在这个示例中,我们将使用本章中 编译 Handlebars 模板 菜谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们修改配置,以便在编译模板之前从模板中移除过多的空白字符。
-
首先,我们可以向
processContent选项提供一个空函数,该函数接收模板的内容并将其传递而不进行修改:handlebars: { blog: { options: { processContent: function (content) { return content; } }, src: 'blog.hbs', dest: 'templates.js' } } -
然后,我们可以修改提供给
processContent的函数的代码,以便从模板的内容中移除所有前导和尾随空白字符:processContent: function (content) { content = content.replace(/^[\x20\t]+/mg, ''); content = content.replace(/[\x20\t]+$/mg, ''); content = content.replace(/^[\r\n]+/, ''); content = content.replace(/[\r\n]*$/, '\n'); return content; } -
如果我们现在使用
grunt handlebars命令运行任务,我们应该看到以下类似的输出:Running "handlebars:blog" (handlebars) task >> 1 file created. -
在
templates.js文件中显示的编译结果现在不应显示任何原始模板中存在的尾随空白字符的迹象。
在编译 Underscore 模板之前进行修改
虽然这种情况并不常见,但可能会有这样的时候,你希望在编译或渲染模板之前修改模板的内容。这通常是在你希望从模板中移除过多空白字符,或者如果你想移除或替换你无法控制的模板的某些部分时所需的。
在这个菜谱中,我们将利用contrib-jst (0.6.0)插件及其jst任务提供的processContent选项,在编译模板之前从模板中移除空白字符。
准备工作
在这个示例中,我们将使用本章“编译 Underscore 模板”菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考。
如何做到这一点...
以下步骤将指导我们修改配置,以便在编译模板之前移除模板中的过多空白字符。
-
首先,我们可以向
processContent选项提供一个空函数,该函数接收模板的内容并将其传递而不做任何修改:jst: { blog: { options: { processContent: function (content) { return content; } }, src: 'blog.html', dest: 'templates.js' } } -
然后,我们可以修改提供给
processContent的函数代码,以便从模板内容中移除所有前导和尾随空白字符:processContent: function (content) { content = content.replace(/^[\x20\t]+/mg, ''); content = content.replace(/[\x20\t]+$/mg, ''); content = content.replace(/^[\r\n]+/, ''); content = content.replace(/[\r\n]*$/, '\n'); return content; } -
如果我们现在使用
grunt jst命令运行任务,我们应该看到以下类似的输出:Running "jst:blog" (jst) task File templates.js created. -
现在在
templates.js文件中编译的结果应该不再显示原始模板中存在的任何尾随空白字符的迹象。
第四章。生成 CSS 和 JavaScript
在本章中,我们将涵盖以下菜谱:
-
编译 LESS 到 CSS
-
编译 Sass 到 CSS
-
编译 Stylus 到 CSS
-
编译 CoffeeScript 到 JavaScript
-
编译 LiveScript 到 JavaScript
-
为 LESS 生成源映射
-
为 Sass 生成源映射
-
为 CoffeeScript 生成源映射
-
使用 LESS 定义自定义函数
-
使用 Stylus 插件
简介
由于今天使用的多数技术都是基于标准的,尤其是由 W3C 维护的标准,而标准的演变相当缓慢,因此一直存在一个开发者社区,他们旨在通过在现有陈旧技术之上构建工具来推动演变。
生成CSS和JavaScript代码已经存在一段时间了,并且已经成为某种行业标准。使用最新的工具生成代码使我们能够比以往任何时候都更快地管理和构建我们的 Web 应用程序。现在,我们可以在代码中使用更多现代语言特性,并且我们终于可以利用我们的编程技能来生成我们的 CSS 样式表。
编译 LESS 到 CSS
在这个菜谱中,我们将使用contrib-less (0.11.4)插件来编译我们的LESS样式表到CSS样式表,这些样式表可以被我们的 Web 应用程序使用。
准备中
在这个例子中,我们将使用我们在第一章中创建的基本项目结构,即在项目中安装 Grunt 菜谱中的使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
如何做到...
以下步骤将引导我们创建一个简单的 LESS 样式表并将其编译为 CSS:
-
我们将按照第一章中提供的安装插件菜谱中的说明,首先安装包含
contrib-less插件的包。 -
让我们在项目目录中创建一个简单的 LESS 文件,名为
styles.less,其内容如下:@first: #ffffff; @second: #000000; body { background-color: @first; background-image: url('../background.png'); color: @second; } -
现在,我们可以在配置中添加以下
less任务,它将编译项目目录中的styles.less文件到styles.css文件:less: { styles: { src: 'styles.less', dest: 'styles.css' } } -
然后,我们可以使用
grunt less命令来运行任务,这将产生类似于以下内容的输出:Running "less:styles" (less) task File styles.css created: 0 B → 56 B -
如果我们现在查看我们的项目目录,我们应该会看到新的
styles.css文件,其内容如下:body { background-color: #ffffff; background-image: url('background.png'); color: #000000; }
更多...
less任务为我们提供了几个有用的选项,可以与它的基本编译功能一起使用。我们将探讨如何指定资源根路径、最小化输出结果、重写 URL 为相对路径、强制评估导入、指定额外的导入路径以及向输出结果添加横幅。
指定资源根路径
如果我们想在 L 文件中的所有 URL 资源前添加路径,我们可以使用rootpath选项,如以下示例所示:
less: {
styles: {
options: {
rootpath: '../'
},
src: 'styles.less',
dest: 'styles.css'
}
}
最小化输出结果
由于我们始终致力于减小我们的 Web 应用的下载大小,我们最终会达到想要通过最小化样式表来减小样式表大小的点。可以使用cleancss选项来指示应该使用clean-css实用程序来压缩编译结果,如以下示例所示:
less: {
styles: {
options: {
cleancss: true
},
src: 'styles.less',
dest: 'styles.css'
}
}
重写 URL 为相对路径
如果我们将一个 LESS 样式表导入到另一个中,对资产的 URL 引用可能会变得有些令人困惑,因为我们必须使用相对于可能想要导入它的任何样式表的 URL 来引用资源。当然,如果我们将相同的样式表导入到两个不同的样式表中,每个样式表的位置都不同,这也会导致问题。
通过启用rewriteUrls选项,我们不必担心在导入样式表时出现不正确的资源引用。编译器将检查正在导入的样式表中引用的所有资源的 URL,并将它们更改为相对于它们新位置的路由。
假设我们有一个以下目录结构,其中包含一个主style.less文件,它导入body.less文件,该文件在项目根目录中引用了background.png图像:
project/
├── background.png
├── styles.less
└── lib
└── body.less
当rewriteUrls选项设置为false时,body.less文件中的url('../background.png')引用在生成的style.css文件中将保持不变。
当rewriteUrls设置为true时,如以下示例所示,它将被更改为url('background.png'),因为这将是style.css文件到background.png文件的新正确相对路径:
less: {
styles: {
options: {
relativeUrls: true
},
src: 'styles.less',
dest: 'styles.css'
}
}
强制评估导入
为了接收缺失导入依赖项的错误消息,我们可以将strictImports选项设置为true,如以下示例所示:
less: {
styles: {
options: {
strictImports: true
},
src: 'styles.less',
dest: 'styles.css'
}
}
指定额外的导入路径
如果我们希望允许我们的 LESS 样式表导入除了通过简单相对路径可用的样式表之外的样式表,我们可以通过使用paths选项来指示在导入文件时应扫描的额外目录。以下示例指示在扫描导入时应该包含项目根目录中的lib目录:
less: {
styles: {
options: {
paths: ['lib']
},
src: 'styles.less',
dest: 'styles.css'
}
}
向输出结果添加横幅
如果我们想在编译后的样式表开头添加一些额外信息,我们可以使用banner选项。以下示例添加了一个包含有关我们正在生成的文件信息的横幅:
less: {
styles: {
options: {
banner: '/* Name: style.css */\n' +
'/* Author: Grunt Wiz */\n' +
'/* License: MIT */\n\n'
},
src: 'styles.less',
dest: 'styles.css'
}
}
编译 Sass 到 CSS
在这个菜谱中,我们将使用sass (0.12.1)插件将我们的Sass样式表编译成CSS样式表,这些样式表可以被我们的 Web 应用使用。
小贴士
contrib-sass 插件比 sass 插件更受欢迎、更成熟,但需要安装 Sass Ruby库,这似乎是本配方中不必要的步骤。
请注意,在撰写本文时,Sass 插件与 Node.js 的0.11.*版本不兼容,但针对此问题的解决方案正在开发中。在下面的配方中,使用了0.10.*版本。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导我们创建一个简单的 Sass 样式表并将其编译为 CSS:
-
我们将按照第一章中“安装插件”配方中的说明安装包含
sass插件的包。 -
让我们在项目目录中创建一个名为
styles.scss的简单 Sass 文件,其内容如下:$first: #ffffff; $second: #000000; body { background-color: $first; background-image: url('background.png'); color: $second; } -
现在,我们可以将以下
sass任务添加到我们的配置中,该任务将编译项目目录中的styles.scss文件到styles.css文件:sass: { styles: { src: 'styles.scss', dest: 'styles.css' } } -
然后,我们可以使用
grunt sass命令运行任务,这将产生类似于以下内容的输出:Running "sass:styles" (sass) task File styles.css created. -
如果我们现在查看我们的项目目录,我们应该看到包含以下内容的新的
styles.css文件:body { background-color: #ffffff; background-image: url('background.png'); color: #000000; }
还有更多...
sass任务为我们提供了几个有用的选项,可以与基本编译功能一起使用。我们将探讨如何更改输出样式、指定图像路径以及指定额外的导入路径。
改变输出样式
在编译 Sass 样式表时,有两种输出样式可用,每种都提供不同级别的可读性。默认情况下,outputStyle选项设置为nested,这会产生更易读的输出。要删除输出中的所有空白,您可以将其设置为compressed,如下例所示:
sass: {
styles: {
options: {
outputStyle: 'compressed'
},
src: 'styles.scss',
dest: 'styles.css'
}
}
指定图像路径
如果您想轻松更改 Sass 样式表中引用的所有图像的路径,sass插件提供了image-url函数。以下示例使用imagePath选项来指示img字符串应添加到指定的每个 URL 之前,使用image-url函数:
sass: {
styles: {
options: {
imagePath: 'img'
},
src: 'styles.scss',
dest: 'styles.css'
}
}
指定额外的导入路径
如果我们希望允许我们的 Sass 样式表导入除简单相对路径之外的其他样式表,我们可以通过使用includePaths选项来指示在导入文件时应扫描的额外目录。以下示例指示在扫描导入时,应包含项目根目录中的lib目录:
sass: {
styles: {
options: {
includePaths: ['lib']
},
src: 'styles.scss',
dest: 'styles.css'
}
}
将 Stylus 编译为 CSS
在这个菜谱中,我们将使用contrib-stylus (0.18.0)插件将我们的Stylus样式表编译成CSS样式表,这些样式表可以被我们的 Web 应用程序使用。
准备工作
在这个例子中,我们将使用我们在第一章中创建的基本项目结构,即第一章的在项目中安装 Grunt菜谱中创建的结构。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导我们创建一个简单的 Stylus 样式表并将其编译成 CSS:
-
我们将首先按照第一章中安装插件菜谱中提供的说明安装包含
contrib-stylus插件的包。 -
让我们在项目目录中创建一个名为
styles.styl的简单 Stylus 文件,其内容如下:first = #ffffff second = #000000 body background-color: first background-image: url('background.png') color: second -
现在,我们可以在配置中添加以下
stylus任务,该任务将编译项目目录中的styles.styl文件到styles.css文件:stylus: { styles: { src: 'styles.styl', dest: 'styles.css' } } -
然后,我们可以使用
grunt stylus命令来运行任务,这应该会产生类似于以下内容的输出:Running "stylus:styles" (stylus) task File styles.css created. -
如果我们现在查看我们的项目目录,我们应该看到新创建的
styles.css文件,其内容如下:body {background-color: #fff; background-image: url("background.png"); color: #000; }小贴士
注意,由于
stylus任务默认会压缩输出,因此此文件的内容可能处于压缩状态。这里以未压缩的形式展示,以便于阅读。
更多内容...
stylus任务为我们提供了几个有用的选项,这些选项可以与其基本的编译功能结合使用。我们将探讨如何禁用结果的 CSS 压缩、指定额外的导入路径、定义全局变量、启用导入 CSS 文件的功能以及向输出添加横幅。
禁用结果的 CSS 压缩
默认情况下,stylus任务会通过删除所有空白来压缩结果的 CSS。这种行为可以通过将compress选项设置为false来禁用,如下例所示:
stylus: {
styles: {
options: {
compress: false
},
src: 'styles.styl',
dest: 'styles.css'
}
}
指定额外的导入路径
如果我们希望允许我们的 Stylus 样式表导入除简单相对路径之外的其他样式表,我们可以通过使用paths选项来指示在导入文件时应扫描的额外目录。以下示例指示在扫描导入时应该包含项目根目录中的lib目录:
stylus: {
styles: {
options: {
paths: ['lib']
},
src: 'styles.styl',
dest: 'styles.css'
}
}
定义全局变量
通过使用define选项,我们可以定义应该对所有目标 LESS 样式表可用的变量。以下示例定义了一个全局可用的keyColor变量:
stylus: {
styles: {
options: {
define: {
keyColor: '#000000'
}
},
src: 'styles.styl',
dest: 'styles.css'
}
}
启用导入 CSS 文件的功能
当在 Stylus 样式表中使用@import语句导入常规 CSS 文件时,编译器的默认行为是生成一个标准的 CSS @import指令,该指令引用文件。通过将'include css'选项设置为true,如下例所示,我们可以指导编译器将指定的文件实际包含到最终结果中:
stylus: {
styles: {
options: {
'include css': true
},
src: 'styles.styl',
dest: 'styles.css'
}
}
添加横幅到输出结果
如果我们想在编译后的样式表中添加一些额外信息到开头,我们可以使用banner选项。以下示例添加了一个包含我们生成文件信息的横幅:
stylus: {
styles: {
options: {
banner: '/* Name: style.css */\n' +
'/* Author: Grunt Wiz */\n' +
'/* License: MIT */\n\n'
},
src: 'styles.styl',
dest: 'styles.css'
}
}
将 CoffeeScript 编译成 JavaScript
在本配方中,我们将使用contrib-coffee (0.11.0)插件将CoffeeScript源文件编译成JavaScript。
准备工作
在本例中,我们将使用第一章中“在项目中安装 Grunt”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考。
如何做到...
以下步骤将指导我们创建一个简单的 CoffeeScript 源文件并将其编译成 JavaScript:
-
我们将首先按照第一章中“安装插件”配方提供的说明安装包含
contrib-coffee插件的包,使用 Grunt 入门。 -
让我们在项目目录中创建一个名为
main.coffee的简单 CoffeeScript 文件,其内容如下:logic = (message) -> console.log(message) message = 'Functionality!' logic(message) -
现在,我们可以在配置中添加以下
coffee任务,它将编译项目目录中的main.coffee文件到main.js文件:coffee: { main: { src: 'main.coffee', dest: 'main.js' } } -
然后,我们可以通过使用
grunt coffee命令来运行任务,这应该会产生类似于以下内容的输出:Running "coffee:main" (coffee) task >> 1 files created. -
如果我们现在查看我们的项目目录,我们应该看到包含以下内容的新的
main.js文件:(function() { var logic, message; logic = function(message) { return console.log(message); }; message = 'Functionality!'; logic(message); }).call(this);
更多...
coffee任务为我们提供了几个有用的选项,这些选项可以与基本编译功能一起使用。我们将探讨如何在没有顶级安全包装器的情况下编译,并在编译前连接多个目标。
在没有顶级安全包装器的情况下编译
默认情况下,CoffeeScript 编译器将生成的编译代码包裹在一个匿名 JavaScript 函数中。这种做法可以防止生成的代码污染全局命名空间,从而避免一系列问题。可以通过使用bare选项来禁用此行为,如下例所示:
coffee: {
main: {
options: {
bare: true
},
src: 'main.coffee',
dest: 'main.js'
}
}
在编译前连接多个目标
当指定多个目标应编译到一个结果文件中时,默认行为是在每个文件单独编译后将其合并在一起。启用顶级安全包装后,这将导致一个包含每个编译文件代码的文件,每个文件都包裹在其自己的顶级函数中。
为了在编译前合并文件并使所有编译内容都在一个顶级包装函数中,我们可以使用join选项,如下例所示:
coffee: {
main: {
options: {
join: true
},
src: ['main.coffee', 'more.coffee'],
dest: 'main.js'
}
}
将 LiveScript 编译成 JavaScript
在这个配方中,我们将使用livescript (0.5.1)插件将LiveScript源文件编译到JavaScript。
准备工作
在这个例子中,我们将使用我们在第一章中在项目中安装 Grunt配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们创建一个简单的 LiveScript 源文件并将其编译成 JavaScript:
-
我们将按照第一章中安装插件配方提供的说明来安装包含
contrib-livescript插件的包,使用 Grunt 入门。 -
让我们在项目目录中创建一个名为
main.ls的简单 LiveScript 文件,其内容如下:logic = (msg) -> console.log(msg) message = 'Functionality!' logic message -
现在,我们可以在配置中添加以下
livescript任务,它将编译项目目录中的main.ls文件到main.js文件:livescript: { main: { src: 'main.ls', dest: 'main.js' } } -
然后,我们可以通过使用
grunt livescript命令来运行任务,它应该产生类似以下内容的输出:Running "livescript:main" (livescript) task File main.js created. -
如果我们现在查看我们的项目目录,我们应该看到包含以下内容的新的
main.js文件:(function(){ var logic, message; logic = function(msg){ return console.log(msg); }; message = 'Functionality!'; logic(message); }).call(this);
为 LESS 生成源映射
在这个配方中,我们将使用contrib-less (0.11.4)插件在将我们的LESS样式表编译到CSS时生成源映射。
准备工作
在这个例子中,我们将使用本章中将 LESS 编译成 CSS配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们将我们的 LESS 样式表编译到一个 CSS 文件中,该文件包含我们的源映射和源样式表:
-
首先,我们将通过在
less任务的配置中将sourceMap选项设置为true来指示我们希望生成源映射:less: { styles: { options: { sourceMap: true }, src: 'styles.less', dest: 'styles.css' } } -
接下来,我们将通过在任务的配置中将
outputSourceFiles选项设置为true来指示我们希望在生成的文件中包含生成的 CSS 的来源:less: { styles: { options: { sourceMap: true, outputSourceFiles: true }, src: 'styles.less', dest: 'styles.css' } } -
然后,我们可以通过使用
grunt less命令来运行任务,它应该产生类似以下内容的输出:Running "less:styles" (less) task File styles.css created: 99 B → 296 B -
如果我们现在查看生成的
styles.css文件,我们应该在文件末尾看到嵌入的源映射和源样式表。
还有更多...
如我们在主要配方中看到的那样,less任务的默认行为是将源映射嵌入到生成的 CSS 文件中。这是生成和消费源映射的最简单方法,但这种方法会增加所有用户的生成文件的大小,从而增加 Web 应用的加载时间。
大多数开发者更喜欢将源映射和 CSS 结果分开,这样普通用户就不需要下载源映射及其引用的源文件。然而,由于需要将源映射和源文件提供给将使用 CSS 文件产生的浏览器,这种设置稍微复杂一些。
以下步骤将指导我们修改配置,以指示源映射和源文件不应包含在生成的 CSS 文件中。
-
首先,我们将通过在
less任务的配置中将sourceMap选项设置为true来指示我们想要生成源映射。less: { styles: { options: { sourceMap: true }, src: 'styles.less', dest: 'styles.css' } } -
然后,我们将修改任务以指示应生成外部源映射。这是通过使用
sourceMapFilename选项指定源映射文件名称来完成的:less: { styles: { options: { sourceMap: true, sourceMapFilename: 'styles.css.map' }, src: 'styles.less', dest: 'styles.css' } } -
然后,我们可以通过使用
grunt less命令来运行任务,它应该产生类似于以下内容的输出:Running "less:styles" (less) task File styles.css.map created. File styles.css created: 99 B → 137 B -
如果我们现在查看我们的项目目录,我们应该能看到
styles.css和styles.css.map文件。现在生成的样式表也应该包含对源映射文件的引用,现在源映射也应该引用styles.less源文件。
它是如何工作的...
利用生成的源映射就像在示例 HTML 文件中包含生成的 CSS 文件一样简单。然而,我们必须确保所有必要的文件都可以被打开 HTML 文件的浏览器访问,在我们的当前例子中,由于所有文件都位于同一目录中,这一点已经得到解决。我们还必须确保我们用来打开 HTML 文件的浏览器支持源映射的使用。
以下步骤将指导我们创建一个包含生成的 CSS 文件的示例 HTML 文件,并在支持源映射的浏览器中打开它。
-
让我们创建一个示例 HTML 文件,它将包括生成的 JavaScript 源文件。让我们在项目目录中创建一个名为
test.html的文件,其内容如下:<html> <head> <title>Less Source Map Test</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <h1>Less Source Map Test</h1> </body> </html> -
在我们的示例 HTML 文件准备就绪后,我们可以使用支持源映射的浏览器打开它。在我们的例子中,我们将使用 Google Chrome 版本 36。以下图像显示了浏览器窗口,开发者工具已打开,并选择了
body元素进行检查。![如何工作...]()
-
如果我们查看位于开发者工具中的样式检查器,我们会看到
styles.less:4正在显示为从该位置派生body元素样式的位置。这表明我们的源映射正在正确工作。
为 Sass 生成源映射
在这个菜谱中,我们将使用 sass (0.12.1) 插件在将我们的 Sass 样式表编译为 CSS 时生成 源映射。
准备工作
在这个例子中,我们将使用我们在本章中 编译 Sass 为 CSS 菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们修改配置,以便在将 Sass 样式表编译为 CSS 时生成源映射:
-
首先,我们将通过在
sass任务的配置中将sourceMap选项设置为true来指示我们想要生成源映射:sass: { styles: { options: { sourceMap: true }, src: 'styles.scss', dest: 'styles.css' } } -
然后,我们可以通过使用
grunt sass命令来运行任务,它应该产生类似于以下内容的输出:Running "sass:styles" (sass) task File styles.css created. File styles.css.map created. -
如果我们现在查看我们的项目目录,我们应该看到
styles.css和styles.css.map文件。生成的样式表现在也应该包含对源映射文件的引用,并且源映射现在也应该引用styles.scss源文件。
工作原理...
利用生成的源映射文件就像在示例 HTML 文件中包含生成的 CSS 文件一样简单。然而,我们必须确保所有必要的文件都能被打开 HTML 文件的浏览器访问,在我们的当前示例中,由于所有文件都位于同一目录下,这一点已经得到处理。我们还必须确保我们用来打开 HTML 文件的浏览器支持源映射的使用。
以下步骤将指导我们创建一个包含生成的 CSS 文件并在支持源映射的浏览器中打开的示例 HTML 文件:
-
让我们创建一个名为
test.html的示例 HTML 文件,它将包含生成的 JavaScript 源文件,并提供以下内容:<html> <head> <title>Sass Source Map Test</title> <link rel="stylesheet" type="text/css" href="styles.css"> </head> <body> <h1>Sass Source Map Test</h1> </body> </html> -
在我们的示例 HTML 文件准备就绪后,我们可以使用支持源映射的浏览器打开它。在我们的例子中,我们将使用 Google Chrome 版本 36。以下图片显示了开发者工具已打开的浏览器窗口,并选中了用于检查的
body元素。![工作原理...]()
-
如果我们查看位于开发者工具中的样式检查器,我们会看到
styles.scss:4正在显示为从该位置派生body元素样式的位置。这表明我们的源映射正在正确工作。
为 CoffeeScript 生成源映射
在这个菜谱中,我们将使用 contrib-coffee (0.11.0) 插件在将我们的 CoffeeScript 源文件编译为 JavaScript 时生成 源映射。
准备工作
在本例中,我们将使用本章“将 CoffeeScript 编译为 JavaScript”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们修改配置以在将 CoffeeScript 源文件编译为 JavaScript 时生成源映射:
-
首先,我们将通过在
coffee任务的配置中将sourceMap选项设置为true来指示我们想要生成源映射:sass: { styles: { options: { sourceMap: true }, src: 'styles.scss', dest: 'styles.css' } } -
然后,我们可以通过使用
grunt coffee命令来运行任务,它应该产生类似于以下内容的输出:Running "coffee:main" (coffee) task >> 1 files created. >> 1 source map files created. -
如果我们现在查看我们的项目目录,我们应该能看到
main.js和main.js.map文件。生成的 JavaScript 源文件现在也应包含对源映射文件的引用,并且源映射现在也应引用main.coffee源文件。
它是如何工作的...
利用生成的源映射就像在示例 HTML 文件中包含生成的 CSS 文件一样简单。然而,我们必须确保所有必要的文件都可以被打开 HTML 文件的浏览器访问,在我们的当前例子中,由于所有文件都位于同一目录,这一点已经得到解决。我们还必须确保我们用来打开 HTML 文件的浏览器支持源映射的使用。
以下步骤将引导我们创建一个包含生成的 JavaScript 文件的示例 HTML 文件,并在支持源映射的浏览器中打开该 HTML 文件:
-
让我们创建一个名为
test.html的示例 HTML 文件,它将包含生成的 JavaScript 源文件,并提供以下内容:<html> <head> <title>CoffeeScript Source Map Test</title> <script type="text/javascript" src="img/main.js"></script> </head> <body> <h1>CoffeeScript Source Map Test</h1> </body> </html> -
在准备好我们的示例 HTML 文件后,我们可以使用支持源映射的浏览器打开它。在我们的例子中,我们将使用 Google Chrome 版本 36。以下图片显示了开发者工具已打开的浏览器窗口:
![它是如何工作的...]()
小贴士
在打开开发者工具后,您可能需要刷新浏览器才能看到下一步骤中讨论的适当控制台输出。
-
如果我们查看开发者工具中控制台的输出,我们会看到
main.coffee:2正被显示为'Functionality!'字符串记录的位置。这表明我们的源映射正在正确工作。
使用 LESS 定义自定义函数
在本配方中,我们将使用 contrib-less (0.11.4) 插件来定义可以在我们的 LESS 样式表中使用的自定义函数。
在我们的例子中,我们将创建一个名为 halfDarken 的自定义函数,该函数将使提供的任何颜色变暗 50%。它将使用 LESS 库的内置 darken 函数。
准备工作
在本例中,我们将使用本章“将 LESS 编译为 CSS”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们修改配置以定义一个自定义函数,并修改我们的 LESS 样式表以使用它:
-
首先,我们将通过添加
customFunctions选项并定义halfDarker函数的占位符来修改我们的less任务配置:less: { styles: { options: { customFunctions: { halfDarken: function (less, color) { return color; } } }, src: 'styles.less', dest: 'styles.css' } } -
然后,我们可以为
halfDarken函数添加一些逻辑,该函数将提供的颜色变暗 50%,并返回结果:halfDarken: function (less, color) { functions = less.tree.functions; return functions.darken(color, {value:50}); } -
一旦我们定义了函数,我们就可以修改我们的
styles.less文件以使用它:@first: #ffffff; @second: #000000; body { background-color: halfDarken(@first); color: @second; } -
我们现在可以通过使用
grunt less命令来运行我们的任务,该命令应该产生以下输出:Running "less:styles" (less) task File styles.css created: 0 B → 56 B -
如果我们现在查看生成的
styles.css文件,我们可以看到halfDarken函数已经将提供的颜色变暗了 50%:body { background-color: #808080; color: #000000; }
使用 Stylus 插件
在这个菜谱中,我们将使用contrib-stylus (0.18.0)插件来编译Stylus样式表,这些样式表使用了开发者社区提供的 Stylus 库之一。
小贴士
可以在以下 URL 找到 Stylus 框架可用的插件的部分列表:
github.com/LearnBoost/stylus/wiki
对于我们的示例,我们将使用Roots平台打包的axis库。它提供了一系列有用的函数、混合和其他实用工具,您肯定会在日常样式表开发中使用到。
准备工作
在这个例子中,我们将使用本章中“将 Stylus 编译为 CSS”菜谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们修改配置,以便我们的样式表能够访问轴库:
-
首先,我们需要使用
npm install --save axis-css命令在我们的本地项目路径上安装axis-css包。这将产生包含以下内容的输出:axis-css@0.1.8 node_modules/axis-css -
现在库已经安装,我们可以修改
stylus任务的配置,以便在编译目标样式表之前加载库。这是通过接受一个数组作为选项use来完成的,该数组包含准备用作 Stylus 插件的导入库。以下配置使用此选项来加载axis-css插件:stylus: { styles: { options: { use: [ require('axis-css') ] }, src: 'styles.styl', dest: 'styles.css' } } -
作为测试,我们还可以在我们的示例样式表中使用轴库提供的一个功能。让我们修改
main.styl的内容,以包含库提供的特殊absolute属性:first = #ffffff second = #000000 body absolute: top left background-color: first color: second -
我们现在可以通过运行
grunt stylus命令来测试我们的设置,该命令应该产生类似于以下内容的输出:Running "stylus:styles" (stylus) task File styles.css created. -
如果我们查看生成的
main.css文件,现在我们会看到它包含由轴库的absolute属性生成的位置属性:body { position: absolute; top: 0; left: 0; background-color: #fff; color: #000; }小贴士
注意,由于触笔任务默认压缩其输出,此文件的内容可能处于压缩状态。为了便于阅读,此处以未压缩的形式呈现内容。
第五章:运行自动化测试
在本章中,我们将介绍以下食谱:
-
运行 Jasmine 测试
-
运行 QUnit 测试
-
运行 NodeUnit 测试
-
运行 Mocha 客户端测试
-
运行 Mocha 服务器端测试
-
使用 Mocha 和 Blanket 为服务器端代码生成覆盖率报告
-
使用 Mocha 和 Blanket 为客户端代码生成覆盖率报告
-
使用 QUnit 和 Istanbul 为客户端代码生成覆盖率报告
简介
随着软件单元的大小和复杂度的增加,每次修改后确保其行为符合其规范可能变得相当耗时。为此,通过增加软件单元的整体可靠性和质量,自动化测试变得非常有价值,无需进行持续的手动测试。
一个项目可以实施各种级别的测试,从函数或类级别的单元测试,到利用整个应用程序堆栈的集成测试。大多数测试框架都提供这一整个范围,可能只需添加一些工具。
与测试相关,还值得提及的是测试驱动开发(test-driven development)的实践,在这种实践中,开发者首先为期望的改进或新功能创建(最初失败的)测试用例,然后进行最小限度的开发以使测试用例通过。为了完成它,开发者将审查编写的代码并将其重构到可接受的标准。
运行 Jasmine 测试
在这个食谱中,我们将使用 contrib-jasmine (0.7.0) 插件在 PhantomJS 浏览器环境中运行我们的自动化 Jasmine 测试。
准备工作
在这个例子中,我们将使用我们在 第一章 中 在项目中安装 Grunt 食谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何做到这一点...
以下步骤将引导我们创建一个示例代码库,一些针对代码库运行的测试,以及设置 Grunt 在 PhantomJS 浏览器环境中为我们运行它们。
-
我们将首先按照 第一章 中 安装插件 食谱提供的说明安装包含
contrib-jasmine插件的包,该食谱位于 第一章,开始使用 Grunt。 -
然后,我们在项目目录中创建一个简单的 JavaScript 源文件,其中包含我们想要测试的函数。让我们称它为
main.js并向其中添加以下内容:function square(x) { return x * x; } -
现在,我们可以编写一个简单的测试套件,使用 Jasmine 框架来测试
square方法。让我们在项目目录中创建一个名为tests.js的文件,并添加以下内容:describe('Square method', function() { it('returns 4 for 2', function () { expect(square(2)).toBe(4); }); it('returns 9 for 3', function () { expect(square(3)).toBe(9); }); it('returns 16 for 4', function () { expect(square(4)).toBe(16); }); }); -
在创建代码库和测试之后,我们现在可以向配置中添加以下
jasmine任务,该任务将加载main.js中的代码,并在tests.js文件中运行测试:jasmine: { src: 'main.js', options: { specs: 'tests.js' } } -
然后,我们可以使用
grunt jasmine命令来运行任务,这将产生以下输出,告知我们测试结果:Running "jasmine:src" (jasmine) task Testing jasmine specs via PhantomJS Square method ✓ returns 4 for 2 ✓ returns 9 for 3 ✓ returns 16 for 4 3 specs in 0.015s. >> 0 failures
更多内容...
jasmine任务为我们提供了几个有用的选项,可以与基本测试运行功能一起使用。我们将探讨如何加载用于测试的辅助工具,如何在运行测试之前加载库,如何加载测试所需的样式,以及如何为规范运行器提供自定义模板。
在测试中加载辅助工具
如果我们想使用自定义的等价性测试器或匹配器,我们可以在运行测试之前使用helpers选项来包含它们。在下面的示例中,我们指示在运行测试之前应加载包含在helpers.js文件中的自定义辅助工具:
jasmine: {
src: 'main.js',
options: {
specs: 'tests.js',
helpers: 'helpers.js'
}
}
在运行测试之前加载库
如果我们想要测试的代码依赖于第三方库,而这些库既没有在源代码、规范或辅助工具中加载,我们可以使用vendor选项来加载它们,如下面的示例所示:
jasmine: {
src: 'main.js',
options: {
specs: 'tests.js',
vendor: ['lodash.min.js']
}
}
加载测试所需的样式
如果我们的测试依赖于浏览器中存在特定的 CSS 样式,我们可以使用styles选项来加载它们,如下面的示例所示:
jasmine: {
src: 'main.js',
options: {
specs: 'tests.js',
styles: 'styles.css'
}
}
为规范运行器提供自定义模板
当编写在浏览器内部运行的源代码的测试时,需要一些 HTML 元素,如固定元素,这种情况可能相当常见。向生成的规范运行器(test.html)添加 HTML 的最简单方法是对其生成的模板进行自定义。
以下步骤将引导我们获取默认规范运行器模板,对其进行自定义,并将其用作我们的模板:
-
默认规范运行器模板可以从
contrib-jasmine插件的存储库中检索并保存到runner.tmpl文件中。小贴士
在撰写本文时,默认规范运行器模板可以从以下链接下载:
github.com/gruntjs/grunt-contrib-jasmine/raw/master/tasks/jasmine/templates/DefaultRunner.tmpl -
一旦我们将默认模板保存为
runner.tmpl,我们就可以对其进行一些修改。在下面的示例中,我们只是添加了一个带有一些文本的元素:<!doctype html> <html> <head> <meta charset="utf-8"> <title>Jasmine Spec Runner</title> <link rel="shortcut icon" type="image/png" href="<%= temp %>/jasmine_favicon.png"> <% css.forEach(function(style){ %> <link rel="stylesheet" type="text/css" href="<%= style %>"> <% }) %> </head> <body> <% with (scripts) { %> <% [].concat(polyfills, jasmine, boot, vendor, helpers, src, specs,reporters).forEach(function(script){ %> <script src="img/<%= script %>"></script> <% }) %> <% }; %> <div id="test">Test</div> </body> </html> -
自定义模板准备就绪后,我们将使用
template选项来指示它应在生成运行器时使用:jasmine: { src: 'main.js', options: { specs: 'tests.js', template: 'runner.tmpl' } } -
这样现在就可以在我们的测试中将
test元素可用,允许我们在规范中包含类似以下测试:describe('Test element', function() { it('has test text', function () { expect(window.test.innerHTML).toBe("Test"); }); });
运行 QUnit 测试
在这个菜谱中,我们将使用contrib-qunit (0.5.2)插件在PhantomJS浏览器环境中运行我们的自动化QUnit测试。
准备中
在本例中,我们将使用在第一章中“在项目中安装 Grunt”食谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们创建一个示例代码库、针对代码库运行的一些测试、设置测试环境以及配置 Grunt 为我们使用 PhantomJS 浏览器运行它们。
-
我们将首先按照第一章中“安装插件”食谱提供的说明来安装包含
contrib-qunit插件的包,该食谱位于“使用 Grunt 入门”。 -
然后,我们将在项目目录中创建一个简单的 JavaScript 源文件,其中包含我们想要测试的函数。让我们称它为
main.js,并为其提供以下内容:function square(x) { return x * x; } -
现在,我们可以使用 QUnit 框架编写一组简单的测试,针对
square方法。让我们在项目目录中创建一个名为tests.js的文件,并包含以下内容:QUnit.test("Square method functionality", function(assert) { assert.equal(square(2), 4); assert.equal(square(3), 9); assert.equal(square(4), 16); }); -
由于测试是在浏览器内运行的,而
contrib-qunit插件不会自动包含它,因此我们必须将 QUnit 库和样式表下载到项目目录中。小贴士
在撰写本文时,QUnit 库及其配套样式表可以从以下链接下载:
-
为了将之前步骤中设置的所有部分整合在一起,我们现在需要创建一个测试环境,该环境将加载我们的代码库、测试和所有必需的库。让我们在我们的项目目录中创建一个名为
test.html的文件,并包含以下内容:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>QUnit basic example</title> <link rel="stylesheet" href="qunit-1.15.0.css"> <script src="img/qunit-1.15.0.js"></script> <script src="img/main.js"></script> <script src="img/tests.js"></script> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> </body> </html> -
现在代码库、测试和测试环境都已就绪,我们可以在配置中添加以下
qunit任务:qunit: { main: { src: 'test.html' } } -
然后,我们可以使用
grunt qunit命令来运行此任务,它应该会生成以下输出,告知我们测试结果:Running "qunit:main" (qunit) task Testing test.html .OK >> 3 assertions passed (18ms)
更多内容...
qunit任务为我们提供了几个有用的选项,可以与基本测试运行功能一起使用。我们将探讨从网络服务器加载测试、在测试失败后继续执行、抑制 PhantomJS 浏览器控制台输出以及向 PhantomJS 实例传递参数。
从网络服务器加载测试
如果我们希望从网络服务器而不是从文件系统上的文件直接加载我们的测试环境及其所有部分,我们可以使用urls选项,并提供我们想要运行的测试环境的绝对 URL。
以下示例将指导我们将所需的文件移动到可以从中提供服务的目录中,设置一个基本的 Web 服务器来提供这些文件,并修改我们的配置以从 Web 服务器测试文件。
-
我们将首先按照第一章中“安装插件”部分的说明来安装包含
contrib-connect插件的包,这部分内容在《使用 Grunt 入门》中。 -
然后,我们可以在项目根目录中创建一个名为
www的目录,并将main.js、qunit-1.15.0.css、qunit-1.15.0.js、test.html和tests.js文件移动到其中。 -
现在,我们可以通过添加以下
connect任务配置来设置基本的 Web 服务器,该服务器将从www目录中提供文件:connect: { server: { options: { base: 'www' } } } -
在服务器准备好为我们所需的测试环境提供文件后,我们可以修改我们的
qunit任务配置,从适当的 URL 加载它:qunit: { main: { options: { urls: ['http://localhost:8000/test.html'] } } } -
要测试我们的设置,我们可以运行
grunt connect qunit命令来启动 Web 服务器并运行其上托管的测试环境。这应该会产生类似于以下输出的结果:Running "connect:server" (connect) task Started connect web server on http://0.0.0.0:8000 Running "qunit:main" (qunit) task Testing http://localhost:8000/test.html .OK >> 3 assertions passed (19ms)
在测试失败后继续执行
qunit任务的默认行为是在任何测试失败时使整个任务失败。这反过来会导致在qunit任务之后排队的任何任务都不会执行。通过将force选项设置为true,正如以下示例中所示,我们可以指示任务本身不应因测试失败而失败:
qunit: {
main: {
src: 'test.html',
options: {
force: true
}
}
}
抑制 PhantomJS 浏览器控制台输出
qunit任务的默认行为是将无头 PhantomJS 浏览器生成的控制台输出打印到任务运行的命令行。如果我们想防止控制台输出被打印,我们可以将console选项设置为false,如下例所示:
qunit: {
main: {
src: 'test.html',
options: {
console: false
}
}
}
向 PhantomJS 实例传递参数
如果我们想在 PhantomJS 进程启动时传递一些选项,我们可以像在命令行中提供一样,将它们与常规选项一起提供。
小贴士
可以在以下 URL 找到 PhantomJS 可执行程序接受的参数列表:
phantomjs.org/api/command-line.html
以下示例通过将load-images选项设置为false来禁用浏览器中图像的加载:
qunit: {
main: {
src: 'test.html',
options: {
'--load-images': false
}
}
}
运行 NodeUnit 测试
在本食谱中,我们将使用contrib-nodeunit (0.4.1)插件来运行我们的自动化NodeUnit测试。
准备工作
在本例中,我们将使用在第一章中“在项目中安装 Grunt”部分的说明中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们创建一个示例代码库,创建一些针对代码库运行的测试,并配置 Grunt 为我们运行它们。
-
我们将首先按照第一章中“安装插件”配方提供的说明安装包含
contrib-nodeunit插件的包。 -
然后,我们将在项目目录中创建一个简单的 JavaScript 源文件,其中包含并导出一个我们想要测试的函数。让我们称它为
main.js,并添加以下内容:module.exports.square = function (x) { return x * x; } -
现在,我们可以使用 NodeUnit 框架编写一组简单的测试,以测试
square方法。我们还需要将square方法导入到我们的测试套件中,因为nodeunit任务不会自动执行此操作。让我们在项目目录中创建一个名为tests.js的文件,并包含以下内容:var square = require('./main').square; module.exports.testSquare = { 'Square method returns 4 for 2': function (test) { test.equal(square(2), 4); test.done(); }, 'Square method returns 9 for 3': function (test) { test.equal(square(2), 4); test.done(); }, 'Square method returns 16 for 4': function (test) { test.equal(square(2), 4); test.done(); } } -
在我们的代码库和测试创建完成后,我们现在可以向我们的配置中添加以下
nodeunit任务,它将运行tests.js文件中包含的测试:nodeunit: { main: { src: 'tests.js' } } -
我们可以使用
grunt nodeunit命令运行此任务,这将产生以下输出,告知我们测试结果:Running "nodeunit:main" (nodeunit) task Testing tests.js...OK >> 3 assertions passed (10ms)
还有更多...
nodeunit任务为我们提供了一些有用的选项,可以与基本测试运行功能一起使用。我们将探讨如何使用替代报告器和将报告器输出发送到文件。
使用替代报告器
如果我们想要改变测试结果显示的方式,我们可以通过指定一个替代报告器来使用替代报告器。
小贴士
reporter选项的默认值是grunt,但它可以被设置为以下 URL 中module.exports对象列出的任何一种报告器:
github.com/caolan/nodeunit/blob/master/lib/reporters/index.js
在以下示例中,我们使用reporter选项来指示我们希望测试结果以 HTML 格式报告:
nodeunit: {
main: {
src: 'tests.js',
options: {
reporter: 'html'
}
}
}
将报告器输出发送到文件
如果我们希望在运行nodeunit任务时将测试报告的结果存储在文件中,我们可以使用reporterOutput选项来指示应该接收它的文件,如下面的示例所示:
nodeunit: {
main: {
src: 'tests.js',
options: {
reporterOutput: 'result.txt'
}
}
}
运行 Mocha 客户端测试
在这个配方中,我们将使用mocha (0.4.11)插件在PhantomJS浏览器环境中运行我们的自动化Mocha测试。
准备工作
在这个示例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即使用 Grunt 入门。如果你还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导我们创建一个示例代码库,创建一些针对代码库运行的测试,并配置 Grunt 在 PhantomJS 浏览器环境中为我们运行它们。
-
我们将首先按照 第一章 中 安装插件 部分的说明来安装包含
mocha插件的包,该部分位于 使用 Grunt 入门。 -
然后,我们将在项目目录中创建一个简单的 JavaScript 源文件,其中包含我们想要测试的函数。让我们称它为
main.js,并为其提供以下内容:function square(x) { return x * x; } -
现在,我们可以使用 Mocha 和 Expect.js 框架为
square方法编写一个简单的测试套件。让我们在项目目录中创建一个名为tests.js的文件,并包含以下内容:describe('Square method', function() { it('returns 4 for 2', function () { expect(square(2)).to.be(4); }); it('returns 9 for 3', function () { expect(square(3)).to.be(9); }); it('returns 16 for 4', function () { expect(square(4)).to.be(16); }); }); -
由于测试是在浏览器内运行的,而
mocha插件不会自动包含它,因此我们需要将 Mocha 库和样式表下载到项目目录中。小贴士
在撰写本文时,Mocha 库及其配套的样式表可以从以下链接下载:
-
由于 Mocha 框架默认不包含 Expect.js 断言库,我们还需要将其下载到我们的项目目录中。
小贴士
在撰写本文时,Expect.js 库可以从以下链接下载:
github.com/LearnBoost/expect.js/raw/master/index.js注意,对于我们的当前示例,我们将下载提到的
index.js文件,并将其文件名更改为expect.js。 -
为了将我们设置的所有部分整合在一起,我们现在需要创建一个测试环境,该环境加载我们的代码库、测试和所有必需的库。让我们在我们的项目目录中创建一个名为
test.html的文件,并包含以下内容:<html> <head> <meta charset="utf-8"> <title>Mocha Tests</title> <link rel="stylesheet" href="mocha.css" /> </head> <body> <div id="mocha"></div> <script src="img/main.js"></script> <script src="img/expect.js"></script> <script src="img/mocha.js"></script> <script> mocha.setup('bdd'); </script> <script src="img/tests.js"></script> </body> </html> -
在我们的代码库、测试和测试环境准备就绪后,我们现在可以向我们的配置中添加以下
mocha任务:mocha: { main: { src: 'test.html', options: { run: true } } }小贴士
mocha任务将代码注入到 PhantomJS 浏览器中,以便在测试运行完成后收集测试结果。为了捕获结果,需要调用mocha.run方法来启动环境中包含的测试执行,这个方法调用需要在代码注入之后运行。将run选项设置为true确保在代码注入完成后调用此方法。 -
我们可以使用
grunt mocha命令来运行此任务,它应该会产生以下输出,告知我们测试结果:Running "mocha:main" (mocha) task Testing: test.html 3 passing (105ms) >> 3 passed! (0.10s)
还有更多...
mocha任务为我们提供了几个有用的选项,可以与它的基本测试运行功能一起使用。我们将探讨从网络服务器加载测试、将报告输出发送到文件、显示 PhantomJS 浏览器的控制台输出、显示源错误、指定 Mocha 测试运行器的选项以及使用替代报告器。
从网络服务器加载测试
如果我们希望从文件系统上的文件而不是从网络服务器加载我们的测试环境及其所有部分,我们可以使用urls选项,通过传递我们想要运行的测试环境的绝对 URL 来实现。
以下示例将指导我们将所需的文件移动到可以从中提供服务的目录,设置一个基本网络服务器以提供这些文件,并修改我们的配置以从网络服务器测试文件。
-
我们将首先按照第一章中安装插件食谱提供的说明安装包含
contrib-connect插件的包。 -
然后,我们可以在项目根目录中创建一个名为
www的目录,并将expect.js、main.js、mocha.css、mocha.js、test.html和tests.js文件移动到其中。 -
现在,我们可以设置基本网络服务器,通过添加以下
connect任务配置来从www目录提供文件:connect: { server: { options: { base: 'www' } } } -
在服务器准备好为测试环境提供文件后,我们现在可以修改
mocha任务的配置,从适当的 URL 加载它:mocha: { main: { options: { run: true, urls: ['http://localhost:8000/test.html'] } } } -
要测试我们的设置,我们可以运行
grunt connect qunit命令以启动网络服务器并运行其上托管的测试环境。这应该会产生类似于以下内容的输出:Running "connect:server" (connect) task Started connect web server on http://0.0.0.0:8000 Running "mocha:main" (mocha) task Testing: http://localhost:8000/test.html 3 passing (110ms) >> 3 passed! (0.11s)
将报告输出发送到文件
如果我们希望在运行mocha任务时将测试的报告结果存储在文件中,我们可以使用以下示例中的dest选项来指定应接收它的文件:
mocha: {
main: {
src: 'test.html',
dest: 'result.txt',
options: {
run: true
}
}
}
显示 PhantomJS 浏览器的控制台输出
默认情况下,当mocha任务运行时,PhantomJS 浏览器的控制台输出不会显示在命令行输出中。如果我们想显示输出,我们可以将log选项设置为true,如下面的示例所示:
mocha: {
main: {
src: 'test.html',
options: {
run: true,
log: true
}
}
}
显示源错误
mocha任务的默认行为是忽略在将要运行的测试源文件中遇到的语法错误。如果遇到错误,源文件将不会加载,这可能会在没有有用错误消息的情况下导致所有测试失败。
如果我们想了解源中遇到的错误,我们可以将logErrors选项设置为true,如下面的示例所示:
mocha: {
main: {
src: 'test.html',
options: {
run: true,
logErrors: true
}
}
}
指定 Mocha 测试运行器的选项
如果我们想要直接将一些选项指定给在mocha任务背后运行的 Mocha 测试运行器,我们可以使用mocha选项提供它们。以下示例使用grep选项来指示仅运行描述中包含字符串'2'的测试:
mocha: {
main: {
src: 'test.html',
options: {
run: true,
mocha: {
grep: '2'
}
}
}
}
小贴士
可用选项及其解释的列表可以在以下 URL 中找到:
使用替代报告器
如果我们想要更改测试结果显示的方式,我们可以通过指定reporter选项来使用替代报告器。
小贴士
reporter选项的默认值是dot,但它可以设置为以下 URL 中列出的任何报告器:
注意,在提及的 URL 中列出的各种报告器的名称,大部分情况下在使用reporter选项指示它们时需要大写。
在以下示例中,我们使用reporter选项来指示我们希望以 JSON 格式报告测试结果:
mocha: {
main: {
src: 'test.html',
options: {
run: true,
reporter: 'JSON'
}
}
}
运行 Mocha 服务器端测试
在这个菜谱中,我们将使用mocha-test (0.11.0)插件来运行我们的自动化Mocha测试。
准备工作
在这个例子中,我们将使用我们在第一章中提供的在项目中安装 Grunt菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们创建一个示例代码库,创建一些要针对代码库运行的测试,并配置 Grunt 为我们运行它们。
-
我们将首先通过遵循第一章中提供的安装插件菜谱中的说明来安装包含
mocha-test插件的包,使用 Grunt 入门。 -
然后,我们可以在我们的项目目录中创建一个 JavaScript 源文件,该文件包含并导出我们想要测试的函数。让我们称它为
main.js,并将其以下内容添加到其中:module.exports.square = function (x) { return x * x; } -
现在,我们可以编写一组简单的测试,使用 Mocha 框架来测试
square方法。我们还需要将square方法和assert库导入到我们的测试套件中,因为mochaTest任务不会自动执行此操作。让我们在项目目录中创建一个名为tests.js的文件,并添加以下内容:var assert = require('assert'); var square = require('./main').square; describe('Square method', function() { it('returns 4 for 2', function () { assert.equal(square(2), 4); }); it('returns 9 for 3', function () { assert.equal(square(3), 9); }); it('returns 16 for 4', function () { assert.equal(square(4), 16); }); }); -
在我们的代码库和测试创建完成后,我们现在可以将以下
mochaTest任务添加到我们的配置中,该任务将运行tests.js文件中的测试:mochaTest: { main: { src: 'tests.js' } } -
然后,我们可以使用
grunt mochaTest命令运行任务,该命令应该会输出测试结果,类似于以下内容:Running "mochaTest:main" (mochaTest) task 3 passing (6ms)
更多内容...
mochaTest 任务为我们提供了几个有用的选项,这些选项可以与它的基本测试运行功能一起使用。我们将探讨如何使用替代报告器、使用正则表达式选择测试、检查全局变量泄漏、将报告器输出发送到文件以及将额外模块加载到测试环境中。
使用替代报告器
如果我们想要更改测试结果显示的方式,可以通过指定 reporter 选项来使用一个替代的报告器。
提示
reporter 选项的默认值是 dot,但它可以设置为以下 URL 中列出的任何报告器之一:
注意,当使用此插件引用时,提到的各种报告器的名称都应该写成小写。
在以下示例中,我们使用 reporter 选项来指示我们希望将测试结果以 JSON 格式报告:
mochaTest: {
main: {
src: 'tests.js',
options: {
reporter: 'json'
}
}
}
使用正则表达式选择测试
很常见的情况是我们只想测试测试套件中的一部分测试。我们可以通过向 grep 选项提供一个 正则表达式 来针对特定的测试进行定位,该正则表达式将与我们的套件中测试的描述进行匹配。
在以下示例中,我们指定只运行描述中包含 '2' 字符串的测试:
mochaTest: {
main: {
src: 'tests.js',
options: {
grep: '2'
}
}
}
检查全局变量泄漏
通常认为在 JavaScript 中使用全局变量是不良做法,因为各种库使用的变量名或由 JavaScript 环境暗示的变量名之间可能会发生冲突。
如果我们希望在源代码或测试代码中遇到全局变量时收到警告,可以将 ignoreLeaks 选项设置为 false。此外,我们还可以使用 globals 选项来指定当作为 globals 定义时应忽略的变量名。
以下示例将全局泄漏检测打开,并指示如果 allowedGlobal 变量名在全局范围内定义,则应忽略它。
mochaTest: {
main: {
src: 'tests.js',
options: {
ignoreLeaks: false,
globals: ['allowedGlobal']
}
}
}
将报告器输出发送到文件
当运行 mochaTest 任务时,如果我们希望将测试的输出结果存储在文件中,可以使用 captureFile 选项来指定接收结果的文件,如下面的示例所示:
mochaTest: {
main: {
src: 'tests.js',
options: {
captureFile: 'result.txt'
}
}
}
将额外模块加载到测试环境中
require 选项可以在运行测试之前将模块加载到我们的测试环境中。这允许我们使用库,而无需将它们导入到每个测试套件中。
在以下示例中,我们加载 should.js 模块,以便在测试中使用 Should.js 库:
mochaTest: {
main: {
src: 'tests.js',
options: {
require: ['should']
}
}
}
提示
注意,为了使此示例正常工作,should 包需要在本地或全局范围内安装。
使用 Mocha 和 Blanket 生成服务器端代码的覆盖率报告
在本菜谱中,我们将使用 blanket (0.0.8)、mocha-test (0.11.0) 和 contrib-copy (0.5.0) 插件来运行我们的自动化 Mocha 测试,同时生成针对它们运行的源代码的 覆盖率报告。
准备工作
在本例中,我们将使用在 第一章 中 在项目中安装 Grunt 菜单创建的基本项目结构,该章节介绍了 开始使用 Grunt。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将引导我们创建一个示例代码库,为代码库编写一些测试用例,并在运行测试的同时配置 Grunt 生成覆盖率报告。
-
我们将首先按照 第一章 中 安装插件 菜单提供的说明安装包含
blanket、mocha-test和contrib-copy插件的包,该章节介绍了 开始使用 Grunt。 -
然后,我们可以创建一个包含并导出两个函数的 JavaScript 源文件,其中我们只测试其中一个。让我们在我们的项目目录中创建名为
src/main.js的文件,并包含以下内容:module.exports = { square: function (x) { return x * x; }, cube: function (x) { return x * x * x; } } -
现在,我们可以编写一组简单的测试,使用 Mocha 框架来测试
square方法。我们还需要将square方法以及assert库导入到我们的测试套件中,因为mochaTest任务不会自动执行此操作。让我们在项目目录中创建名为tests/main.js的文件,并包含以下内容:var assert = require('assert'); var square = require('../src/main').square; describe('Square method', function() { it('returns 4 for 2', function () { assert.equal(square(2), 4); }); it('returns 9 for 3', function () { assert.equal(square(3), 9); }); it('returns 16 for 4', function () { assert.equal(square(4), 16); }); }); -
现在我们已经建立了代码库,我们可以通过添加以下
blanket任务到我们的配置中来设置其 仪器化:blanket: { main: { src: 'src/', dest: 'coverage/src/' } } -
为了让我们的测试能够使用我们代码库的仪器化版本,我们现在还应该在配置中添加以下
copy任务,该任务将测试用例复制到可以访问到仪器化代码库的位置,就像它访问常规代码库一样:copy: { tests: { src: 'tests/main.js', dest: 'coverage/tests/main.js' } } -
为了将这些步骤串联起来,我们在配置中添加了以下
mochaTest任务,该任务将在测试用例可以访问到经过仪器化的代码库的位置运行测试,这表明它应该以html-cov格式报告结果,并最终将其保存到result.html文件中:mochaTest: { main: { options: { quiet: true, reporter: 'html-cov', captureFile: 'result.html' }, src: 'coverage/tests/main.js' } } -
我们现在可以通过运行
grunt blanket copy mochaTest命令来测试我们的设置,这应该在项目目录中生成一个名为result.html的文件,其外观如下:![如何操作...]()
更多内容...
本菜谱中讨论的插件组合可以产生多种配置,因此我们将只关注最常见的要求,即以 LCOV 格式报告结果。
以 LCOV 格式输出覆盖率结果
代码覆盖率结果的 LCOV 格式在用于报告和分析的服务中非常流行。
提示
Coveralls 服务是一个很好的例子,你可以将生成的 LCOV 结果发送到该服务以跟踪其历史记录并提供更图形化的表示。
更多信息请参阅 coveralls.io/。
以下步骤将指导我们安装 Mocha 的 LCOV 报告器,并修改我们的配置以使用它。
-
我们将按照 第一章 中 安装插件 菜谱提供的说明,安装包含
mocha-lcov-reporter插件的包。 -
现在,我们可以修改
mochaTest任务的配置,以使用新安装的报告器输出结果:mochaTest: { main: { options: { quiet: true, reporter: 'mocha-lcov-reporter', captureFile: 'result.lcov' }, src: 'coverage/tests/main.js' } } -
我们现在可以通过运行
grunt blanket copy mochaTest命令来测试我们的设置。这应该在项目目录中生成一个名为result.lcov的文件,该文件已准备好提供给众多可用服务之一。
使用 Mocha 和 Blanket 生成客户端代码覆盖率报告
在本菜谱中,我们将使用 blanket-mocha (0.4.1) 插件在 PhantomJS 环境中运行我们的自动化 Mocha 测试,使用 Blanket.js 库为它们运行的源代码生成 覆盖率报告,并将结果与指定的阈值进行比较。
准备工作
在本例中,我们将使用在 第一章 中创建的基本项目结构,即 在项目中安装 Grunt 菜谱,使用 Grunt 入门。如果你还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导我们创建一个示例代码库,创建一些针对它的测试,并配置 Grunt 以生成覆盖率报告并将结果与阈值进行比较。
-
我们将按照 第一章 中 安装插件 菜谱提供的说明,安装包含
blanket-mocha插件的包。 -
然后,我们在项目目录中创建一个简单的 JavaScript 源文件,其中包含两个函数,其中一个我们将对其进行测试。让我们称它为
main.js并向其中添加以下内容:function square(x) { return x * x; } function cube(x) { return x * x * x; } -
现在,我们可以编写一个简单的测试套件,使用 Mocha 和 Expect.js 框架对
square方法进行测试。让我们在项目目录中创建一个名为tests.js的文件,并添加以下内容:describe('Square method', function() { it('returns 4 for 2', function () { expect(square(2)).to.be(4); }); it('returns 9 for 3', function () { expect(square(3)).to.be(9); }); it('returns 16 for 4', function () { expect(square(4)).to.be(16); }); }); -
由于测试是在浏览器内运行的,并且
blanket_mocha插件不会自动包含它,因此我们不得不将 Mocha 库和样式表下载到项目目录中。小贴士
在撰写本文时,Mocha 库及其配套样式表可以从以下链接下载:
-
由于 Mocha 框架默认不包含 Expect.js 断言库,我们还需要将其下载到我们的项目目录中。
提示
在撰写本文时,可以从以下链接下载 Expect.js 库:
github.com/LearnBoost/expect.js/raw/master/index.js注意,对于我们的当前示例,我们将下载提到的
index.js文件,并将其文件名更改为expect.js。 -
我们还必须手动将 Blanket.js 客户端库包含到我们的测试设置中,这意味着我们必须将其下载到我们的项目目录中。
提示
在撰写本文时,可以从以下链接下载 Blanket.js 客户端库:
github.com/alex-seville/blanket/raw/master/dist/qunit/blanket.js -
为了将 Mocha 和 Blanket.js 库连接起来,我们还需要包含一个适配器,该适配器也应下载到我们的项目目录中。
提示
在撰写本文时,可以从以下链接下载 Mocha 到 Blanket.js 的适配器:
github.com/ModelN/grunt-blanket-mocha/raw/master/support/mocha-blanket.js -
由于
blanket_mocha任务需要将 Blanket.js 结果反馈给 Grunt,因此我们需要将适当的报告器下载到我们的项目目录中。提示
在撰写本文时,可以从以下链接下载适当的 Blanket.js 报告器:
github.com/ModelN/grunt-blanket-mocha/raw/master/support/grunt-reporter.js -
为了将我们设置的所有部分整合在一起,我们现在需要创建一个测试环境,该环境加载我们的代码库、测试和所有必需的库。让我们在我们的项目目录中创建一个名为
test.html的文件,并包含以下内容:<html> <head> <meta charset="utf-8"> <title>Mocha Tests</title> <link rel="stylesheet" href="mocha.css" /> </head> <body> <div id="mocha"></div> <script src="img/expect.js"></script> <script src="img/mocha.js"></script> <script src="img/main.js" data-cover></script> <script src="img/blanket.js"></script> <script src="img/mocha-blanket.js"></script> <script> mocha.setup('bdd'); if (window.PHANTOMJS) { blanket.options('reporter', 'grunt-reporter.js'); } </script> <script src="img/tests.js"></script> </body> </html>提示
注意,
script标签中提供的data-cover属性导入了main.js代码库。此属性应包含在导入需要生成覆盖率报告的文件的每个script标签中。 -
在我们的代码库、测试和测试环境准备就绪后,我们现在可以将以下
blanket_mocha任务添加到我们的配置中:blanket_mocha: { main: { src: ['test.html'], options: { threshold: 70 } } }提示
我们在这里使用
threshold选项来指示每个文件应达到的最小代码覆盖率百分比。如果最终百分比低于此阈值,任务将返回失败。 -
然后,我们可以使用
grunt blanket_mocha命令运行任务,这将产生输出,告知我们测试和覆盖率结果,类似于以下内容:Running "blanket_mocha:main" (blanket_mocha) task Testing: test.html 3 passing (3ms) Per-File Coverage Results: (70% minimum) PASS : 1 files passed coverage Unit Test Results: 3 specs passed! (0.00s) >> No issues found.
还有更多...
blanket-mocha任务为我们提供了几个有用的选项,可以与它的基本测试运行和覆盖率报告功能一起使用。我们将探讨指定全局平均的成功阈值、指定特定文件的成功阈值以及指定特定模块的成功阈值。
指定全局平均的成功阈值
如果我们想要设置一个阈值,使得目标源代码中所有文件的平均覆盖率百分比应超过,我们可以使用globalThreshold选项,如下例所示:
blanket_mocha: {
main: {
src: ['test.html'],
options: {
threshold: 70,
globalThreshold: 70
}
}
}
指定特定文件的成功阈值
此外,还可以使用customThreshold选项指定特定文件应遵守的阈值,如下例所示:
blanket_mocha: {
main: {
src: ['test.html'],
options: {
threshold: 70,
customThreshold: {
'main.js': 70
}
}
}
}
指定特定模块的成功阈值
如果我们想要为模块应遵守的平均覆盖率百分比指定一个阈值,我们可以使用modulePattern、moduleThreshold和customModuleThreshold选项。
modulePattern选项接受一个正则表达式,其中第一个分组将用于确定模块的名称。moduleThreshold选项用于指示所有识别的模块应遵守的平均阈值,而customModuleThreshold选项可以用来指定每个特定模块的阈值。
以下示例通过文件名中src目录之后的第一个目录名称来识别模块,检查所有模块的平均代码覆盖率百分比是否高于70,以及one模块的代码覆盖率百分比是否高于60:
blanket_mocha: {
main: {
src: ['test.html'],
options: {
threshold: 70,
modulePattern: './src/(.*?)/',
moduleThreshold: 70,
customModuleThreshold: {
one: 60
}
}
}
}
使用 QUnit 和 Istanbul 生成客户端代码的覆盖率报告
在本配方中,我们将使用qunit-istanbul (0.4.5)插件在PhantomJS环境中运行我们的自动化QUnit测试,使用Istanbul库为他们运行的源代码生成覆盖率报告,并将结果与指定的阈值进行比较。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们创建一个示例代码库,创建一些针对它运行的测试,并配置 Grunt 以生成覆盖率报告并将结果与阈值进行比较。
-
我们将首先按照 第一章 中 安装插件 菜单提供的说明安装包含
qunit-istanbul插件的包。 -
然后,我们将在项目目录中创建一个简单的 JavaScript 源文件,其中包含两个函数,其中一个我们将对其进行测试。让我们称它为
main.js并提供以下内容:function square(x) { return x * x; } function cube(x) { return x * x * x; } -
现在,我们可以编写一组简单的测试,使用 QUnit 框架对
square方法进行测试。让我们在项目目录中创建一个名为tests.js的文件,并包含以下内容:QUnit.test("Square method functionality", function(assert) { assert.equal(square(2), 4); assert.equal(square(3), 9); assert.equal(square(4), 16); }); -
由于测试是在浏览器内运行的,并且
qunit-istanbul插件不会自动包含它,因此我们需要将 QUnit 库和样式表下载到项目目录中。小贴士
在撰写本文时,QUnit 库及其配套样式表可以在以下链接找到:
-
为了将之前步骤中设置的所有部分组合在一起,我们现在需要创建一个测试环境,该环境加载我们的代码库、测试和所有必需的库。让我们在我们的项目目录中创建一个名为
test.html的文件,并包含以下内容:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>QUnit basic example</title> <link rel="stylesheet" href="qunit-1.15.0.css"> <script src="img/qunit-1.15.0.js"></script> <script src="img/main.js"></script> <script src="img/tests.js"></script> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> </body> </html> -
现在,代码库、测试和测试环境都已就绪,我们可以将以下
qunit任务添加到我们的配置中:qunit: { main: { options: { coverage: { src: 'main.js', instrumentedFiles: 'temp/' } }, src: 'test.html' } }小贴士
注意,
qunit任务提供的所有选项都可以用于istanbul任务的options对象中。只有options对象中coverage部分的选项用于指示此插件特有的行为。src选项用于指示我们希望在覆盖率报告中涵盖的源文件。此选项也可以设置为数组以指示更多文件,并且还可以使用标准的 Grunt 全局匹配模式。instrumentedFiles选项用于指示在测试运行期间将包含已仪器化文件的临时目录。请注意,此目录会自动创建和销毁。 -
然后,我们可以使用
grunt qunit命令运行任务,这将产生输出,告知我们测试和覆盖率结果,类似于以下内容:Running "qunit:main" (qunit) task Testing test.html .OK >> 3 assertions passed (20ms) >> Coverage: >> - Lines: 75% >> - Statements: 75% >> - Functions: 50% >> - Branches: 100%
还有更多...
qunit-istanbul 任务为我们提供了几个有用的选项,可以与基本测试运行和覆盖率报告功能一起使用。我们将探讨如何指定报告输出目的地和不同级别的覆盖率阈值。
指定报告输出目的地
如果我们希望将覆盖率报告结果保存到文件中,可以通过提供目录名称给以下示例中的任何突出显示的选项来实现,每个选项都将结果保存为名称中提到的特定格式:
qunit: {
main: {
options: {
coverage: {
src: 'main.js',
instrumentedFiles: 'temp/',
htmlReport: 'html/',
coberturaReport: 'corb/',
lcovReport: 'lcov/',
cloverReport: 'clover/'
}
},
src: 'test.html'
}
}
在不同级别指定覆盖率阈值
如果我们想指定代码覆盖率百分比阈值,无论是行、语句、函数还是分支级别,我们可以通过使用以下示例中突出显示的任何适当命名的选项来实现:
qunit: {
main: {
options: {
coverage: {
src: 'main.js',
instrumentedFiles: 'temp/',
linesThresholdPct: 50,
statementsThresholdPct: 60,
functionsThresholdPct: 70,
branchesThresholdPct: 80
}
},
src: 'test.html'
}
}
第六章:部署准备
在本章中,我们将介绍以下食谱:
-
压缩 HTML
-
压缩 CSS
-
优化图像
-
检查 JavaScript 代码
-
压缩 JavaScript 代码
-
设置 RequireJS
简介
一旦我们的网络应用程序构建完成并且稳定性得到保证,我们就可以开始为将其部署到目标市场做准备。这主要涉及优化构成应用程序的资产。在这个背景下,优化主要指的是某种形式的压缩,其中一些可能会带来性能提升。
专注于压缩主要是由于资产越小,它从托管位置传输到用户网络浏览器就越快。这导致用户体验大大改善,有时对于应用程序的功能也是必不可少的。
压缩 HTML
在这个食谱中,我们使用 contrib-htmlmin (0.3.0) 插件通过压缩来减小一些 HTML 文档的大小。
准备工作
在这个例子中,我们将使用我们在 第一章 中 在项目中安装 Grunt 食谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们创建一个示例 HTML 文档并配置一个压缩它的任务:
-
我们将按照 第一章 中 安装插件 食谱中提供的说明,安装包含
contrib-htmlmin插件的包。 -
接下来,我们将在
src目录中创建一个名为index.html的简单 HTML 文档,我们希望对其进行压缩,并在其中添加以下内容:<html> <head> <title>Test Page</title> </head> <body> <!-- This is a comment! --> <h1>This is a test page.</h1> </body> </html> -
现在,我们将向我们的配置中添加以下
htmlmin任务,该任务表示我们希望从src/index.html文件中移除空白和注释,并且希望将结果保存到dist/index.html文件中:htmlmin: { dist: { src: 'src/index.html', dest: 'dist/index.html', options: { removeComments: true, collapseWhitespace: true } } }小贴士
这里使用
removeComments和collapseWhitespace选项作为示例,因为使用默认的htmlmin任务将不会有任何效果。其他压缩选项可以在以下网址找到: -
我们现在可以使用
grunt htmlmin命令运行任务,它应该产生类似于以下内容的输出:Running "htmlmin:dist" (htmlmin) task Minified dist/index.html 147 B → 92 B -
如果我们现在查看
dist/index.html文件,我们会看到所有空白和注释已经被移除:<html> <head> <title>Test Page</title> </head> <body> <h1>This is a test page.</h1> </body> </html>
压缩 CSS
在这个食谱中,我们将使用 contrib-cssmin (0.10.0) 插件通过压缩来减小一些 CSS 文档的大小。
准备工作
在这个示例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何做到这一点...
以下步骤将指导我们创建一个示例 CSS 文档并配置一个将其压缩的任务。
-
我们将按照第一章中“安装插件”配方提供的说明,安装包含
contrib-cssmin插件的包,该配方位于《使用 Grunt 入门》。 -
然后,我们将在
src目录中创建一个名为style.css的简单 CSS 文档,我们希望将其压缩,并提供以下内容:body { /* Average body style */ background-color: #ffffff; color: #000000; /*! Black (Special) */ } -
现在,我们将添加以下
cssmin任务到我们的配置中,该任务表示我们希望将src/style.css文件压缩,并将结果保存到dist/style.min.css文件中:cssmin: { dist: { src: 'src/style.css', dest: 'dist/style.min.css' } } -
我们现在可以使用
grunt cssmin命令运行此任务,它应该产生以下输出:Running "cssmin:dist" (cssmin) task File dist/style.css created: 55 B → 38 B -
如果我们查看生成的
dist/style.min.css文件,我们将看到它包含原始src/style.css文件的压缩内容:body{background-color:#fff;color:#000;/*! Black (Special) */}
还有更多...
cssmin任务为我们提供了几个有用的选项,可以与它的基本压缩功能一起使用。我们将探讨添加标题、移除特殊注释和报告 gzip 结果。
添加标题
如果我们希望在生成的 CSS 文件中自动包含有关压缩结果的一些信息,我们可以在标题中这样做。可以通过向banner选项提供所需的标题内容来将标题添加到结果之前,如下面的示例所示:
cssmin: {
dist: {
src: 'src/style.css',
dest: 'dist/style.min.css',
options: {
banner: '/* Minified version of style.css */'
}
}
}
移除特殊注释
应该在压缩过程中保留的注释称为特殊注释,可以使用"/*! comment */"标记来表示。默认情况下,cssmin任务将保留所有特殊注释,但我们可以通过使用keepSpecialComments选项来改变这种行为。
keepSpecialComments选项可以设置为*、1或0的值。*值是默认值,表示应保留所有特殊注释,1表示仅保留找到的第一个注释,而0表示不应保留任何注释。以下配置将确保从我们的压缩结果中移除所有注释:
cssmin: {
dist: {
src: 'src/style.css',
dest: 'dist/style.min.css',
options: {
keepSpecialComments: 0
}
}
}
报告 gzip 结果
报告功能有助于查看cssmin任务如何压缩我们的 CSS 文件。默认情况下,将显示目标文件的大小和压缩结果的大小,但如果我们还想看到结果的 gzip 大小,可以将report选项设置为gzip,如下面的示例所示:
cssmin: {
dist: {
src: 'src/main.css',
dest: 'dist/main.css',
options: {
report: 'gzip'
}
}
}
优化图像
在这个菜谱中,我们将使用contrib-imagemin (0.9.4)插件通过尽可能压缩图片来减小图片大小,同时不牺牲其质量。此插件还提供了一个自己的插件框架,将在本菜谱的末尾进行讨论。
准备工作
在本例中,我们将使用我们在第一章中创建的基本项目结构,即“在项目中安装 Grunt”菜谱中的内容,即使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们配置一个将压缩项目中的图片的任务。
-
我们将首先按照第一章中“安装插件”菜谱中的说明安装包含
contrib-imagemin插件的包。 -
接下来,我们可以确保在
src目录中有一个名为image.jpg的图片,我们希望对其进行优化。小贴士
在本例的其余部分,我们将使用与本菜谱一起提供的示例代码中的示例图片。
-
现在,我们将添加以下
imagemin任务到我们的配置中,并指出我们希望对src/image.jpg文件进行优化,并将结果保存到dist/image.jpg文件中:imagemin: { dist: { src: 'src/image.jpg', dest: 'dist/image.jpg' } } -
然后,我们可以使用
grunt imagemin命令来运行任务,这应该会产生以下输出:Running "imagemin:dist" (imagemin) task Minified 1 image (saved 13.36 kB) -
如果我们现在查看
dist/image.jpg文件,我们将看到其大小已经减小,而质量没有受到影响。
更多内容...
imagemin任务为我们提供了几个选项,允许我们调整其优化功能。我们将探讨如何调整PNG压缩级别,禁用渐进式JPEG生成,禁用交错GIF生成,指定要使用的SVGO插件,以及使用imagemin插件框架。
调整 PNG 压缩级别
通过多次运行压缩算法,可以增加 PNG 图片的压缩。默认情况下,压缩算法运行 16 次。可以通过向optimizationLevel选项提供一个从0到7的数字来更改此数字。0值表示压缩实际上被禁用,而7表示算法应该运行 240 次。在以下配置中,我们将压缩级别设置为最大值:
imagemin: {
dist: {
src: 'src/image.png',
dest: 'dist/image.png',
options: {
optimizationLevel: 7
}
}
}
禁用渐进式 JPEG 生成
渐进式 JPEG 是通过多次压缩来压缩的,这使得它们的一个低质量版本可以快速变得可见,并在接收其余图像时提高质量。这在通过较慢的连接显示图像时特别有帮助。
默认情况下,imagemin 插件会以渐进式格式生成 JPEG 图像,但可以通过将 progressive 选项设置为 false 来禁用此行为,如下面的示例所示:
imagemin: {
dist: {
src: 'src/image.jpg',
dest: 'dist/image.jpg',
options: {
progressive: false
}
}
}
禁用交错 GIF 生成
交错 GIF 与渐进式 JPEG 相当,因为它允许在完全下载之前以较低分辨率显示包含的图像,并在接收其余图像时提高质量。
默认情况下,imagemin 插件会以交错格式生成 GIF 图像,但可以通过将 interlaced 选项设置为 false 来禁用此行为,如下面的示例所示:
imagemin: {
dist: {
src: 'src/image.gif',
dest: 'dist/image.gif',
options: {
interlaced: false
}
}
}
指定要使用的 SVGO 插件
在优化 SVG 图像时,默认使用 SVGO 库。这允许我们指定使用 SVGO 库提供的各种插件,每个插件在目标文件上执行特定功能。
小贴士
请参考以下网址以获取有关如何使用 svgo 插件选项和 SVGO 库的更详细说明:
github.com/sindresorhus/grunt-svgmin#available-optionsplugins
库中的大多数插件默认启用,但如果我们想特别指出哪些应该使用,我们可以通过使用 svgoPlugins 选项来实现。在这里,我们可以提供一个对象数组,其中每个对象包含一个属性,表示要影响的插件名称,后跟一个 true 或 false 值,以指示是否激活。以下配置禁用了三个默认插件:
imagemin: {
dist: {
src: 'src/image.svg',
dest: 'dist/image.svg',
options: {
svgoPlugins: [
{removeViewBox:false},
{removeUselessStrokeAndFill:false},
{removeEmptyAttrs:false}
]
}
}
}
使用 imagemin 插件框架
为了支持各种图像优化项目,imagemin 插件拥有自己的插件框架,允许开发者轻松创建一个扩展,以便使用他们所需的工具。
小贴士
您可以在以下网址获取 imagemin 插件框架的可用插件模块列表:
www.npmjs.com/browse/keyword/imageminplugin
以下步骤将指导我们安装并使用 mozjpeg 插件来压缩项目中的图像。这些步骤从主配方开始。
-
我们将首先使用
npm install imagemin-mozjpeg命令安装imagemin-mozjpeg包,这将产生以下输出:imagemin-mozjpeg@4.0.0 node_modules/imagemin-mozjpeg -
包安装完成后,我们需要将其导入到配置文件中,以便在任务配置中使用它。我们通过在
Gruntfile.js文件的顶部添加以下行来实现:var mozjpeg = require('imagemin-mozjpeg'); -
插件安装并导入后,我们现在可以更改
imagemin任务的配置,通过添加use选项并提供初始化的插件:imagemin: { dist: { src: 'src/image.jpg', dest: 'dist/image.jpg', options: { use: [mozjpeg()] } } } -
最后,我们可以通过运行
grunt imagemin命令来测试我们的设置。这应该会产生类似于以下输出的结果:Running "imagemin:dist" (imagemin) task Minified 1 image (saved 9.88 kB)
检查 JavaScript 代码
在这个菜谱中,我们将使用contrib-jshint (0.11.1)插件来检测 JavaScript 代码中的错误和潜在问题。它也常用于在团队或项目中强制执行代码约定。从其名称可以推断出,它基本上是 JSHint 工具的 Grunt 适配器。
准备工作
在这个例子中,我们将使用我们在第一章中在项目中安装 Grunt菜谱中创建的基本项目结构。如果你还不熟悉其内容,请务必参考它。
如何做...
以下步骤将引导我们创建一个样本 JavaScript 文件,并配置一个任务,使用 JSHint 工具扫描和分析它。
-
我们将按照第一章中安装插件菜谱提供的说明安装包含
contrib-jshint插件的包,该菜谱在使用 Grunt 入门中。 -
接下来,我们将在
src目录中创建一个名为main.js的样本 JavaScript 文件,并在其中添加以下内容:sample = 'abc'; console.log(sample); -
我们的样本文件准备好了,现在我们可以将以下
jshint任务添加到我们的配置中。我们将配置这个任务以针对样本文件,并添加一个基本选项,这是我们在这个例子中需要的:jshint: { main: { options: { undef: true }, src: ['src/main.js'] } }小贴士
undef选项是专门用于此示例的标准 JSHint 选项,并且对于此插件的功能不是必需的。指定此选项表示我们希望对未明确定义就使用的变量引发错误。 -
我们现在可以使用
grunt jshint命令来运行任务,这将生成输出,告诉我们样本文件中存在的问题:Running "jshint:main" (jshint) task src/main.js 1 |sample = 'abc'; ^ 'sample' is not defined. 2 |console.log(sample); ^ 'console' is not defined. 2 |console.log(sample); ^ 'sample' is not defined. >> 3 errors in 1 file
还有更多...
jshint任务为我们提供了几个选项,允许我们更改其一般行为,以及它如何分析目标代码。我们将探讨如何指定标准 JSHint 选项,指定全局定义的变量,将报告的输出发送到文件,以及在 JSHint 错误发生时防止任务失败。
指定标准 JSHint 选项
contrib-jshint插件提供了一种简单的方法,将任务选项对象中的所有标准 JSHint 选项传递给底层的 JSHint 工具。
小贴士
JSHint 工具提供的所有选项的列表可以在以下 URL 找到:
以下示例将curly选项添加到我们在主菜谱中创建的任务中,以强制在适当的地方使用花括号:
jshint: {
main: {
options: {
undef: true,
curly: true
},
src: ['src/main.js']
}
}
指定全局定义的变量
利用全局定义的变量在处理 JavaScript 时相当常见,这就是globals选项派上用场的地方。使用此选项,我们可以定义一组将在目标代码中使用的全局值,这样当 JSHint 遇到它们时就不会引发错误。
在以下示例中,我们指出应将 console 变量视为全局变量,并在遇到时不会引发错误:
jshint: {
main: {
options: {
undef: true,
globals: {
console: true
}
},
src: ['src/main.js']
}
}
将报告输出发送到文件
如果我们希望将 JSHint 分析的结果输出存储起来,我们可以通过指定一个文件路径来使用 reporterOutput 选项,如下面的示例所示:
jshint: {
main: {
options: {
undef: true,
reporterOutput: 'report.dat'
},
src: ['src/main.js']
}
}
防止任务因 JSHint 错误而失败
对于 jshint 任务,默认行为是在任何目标文件中遇到 JSHint 错误时退出正在运行的 Grunt 进程。如果你希望即使在出现错误的情况下也能继续监视文件变化,这种行为尤其不受欢迎。
在以下示例中,我们指出当遇到错误时,我们希望保持进程运行,通过将 force 选项设置为 true 值来实现:
jshint: {
main: {
options: {
undef: true,
force: true
},
src: ['src/main.js']
}
}
混淆 JavaScript 代码
在本食谱中,我们将使用 contrib-uglify (0.8.0) 插件来压缩和混淆包含 JavaScript 代码的一些文件。
在很大程度上,压缩过程只是删除源代码文件中的所有不必要的字符并缩短变量名。这有可能显著减小文件大小,略微提高性能,并使你公开可用的代码的内部工作原理更加神秘。
准备工作
在本例中,我们将使用在 第一章 中创建的基本项目结构,即 第一章 的 在项目中安装 Grunt 食谱,使用 Grunt 入门。如果你还不熟悉其内容,请务必参考。
如何操作...
以下步骤将引导我们创建一个示例 JavaScript 文件并配置一个将对其进行混淆的任务。
-
我们将首先按照 第一章 中提供的 安装插件 食谱中的说明安装包含
contrib-uglify插件的包,使用 Grunt 入门。 -
然后,我们可以在
src目录中创建一个名为main.js的示例 JavaScript 文件,我们希望对其进行压缩,并为其提供以下内容:var main = function () { var one = 'Hello' + ' '; var two = 'World'; var result = one + two; console.log(result); }; -
在我们的示例文件准备就绪后,我们现在可以向配置中添加以下
uglify任务,指定示例文件为目标并提供一个输出文件目的地:uglify: { main: { src: 'src/main.js', dest: 'dist/main.js' } } -
现在,我们可以使用
grunt uglify命令运行任务,它应该产生类似于以下内容的输出:Running "uglify:main" (uglify) task >> 1 file created. -
如果我们现在查看生成的
dist/main.js文件,我们应该看到它包含原始src/main.js文件的压缩内容。
还有更多...
uglify 任务为我们提供了几个选项,允许我们更改其一般行为并查看它如何混淆目标代码。我们将探讨指定标准 UglifyJS 选项、生成源映射以及将生成的代码包装在封装器中。
指定标准 UglifyJS 选项
基础的 UglifyJS 工具可以为它的每个独立功能部分提供一组选项。这些部分是扭曲器、压缩器和美化器。contrib-plugin插件允许使用mangle、compress和beautify选项将这些选项传递给这些部分。
小贴士
对于扭曲器、压缩器和美化器各部分的可用选项,可以在以下每个 URL 中找到(按提到的顺序列出):
github.com/mishoo/UglifyJS2#mangler-options
github.com/mishoo/UglifyJS2#compressor-options
github.com/mishoo/UglifyJS2#beautifier-options
以下示例修改了主要菜谱的配置,为这些部分中的每一个提供一个单独的选项:
uglify: {
main: {
src: 'src/main.js',
dest: 'dist/main.js',
options: {
mangle: {
toplevel: true
},
compress: {
evaluate: false
},
beautify: {
semicolons: false
}
}
}
}
生成源映射
随着代码被扭曲和压缩,它对人类来说变得几乎无法阅读,因此,调试几乎变得不可能。正因为如此,我们才有了在压缩代码时生成源映射的选项。
以下示例使用sourceMap选项来表示我们希望生成与我们的扭曲代码一起的源映射:
uglify: {
main: {
src: 'src/main.js',
dest: 'dist/main.js',
options: {
sourceMap: true
}
}
}
运行修改后的任务现在除了包含我们扭曲源代码的dist/main.js文件外,还会在扭曲文件所在的同一目录中生成一个名为main.js.map的源映射文件。
将生成的代码包装在封装器中
当构建自己的 JavaScript 代码模块时,通常有一个好主意是将它们包装在一个包装函数中,以确保你不会将不会在模块本身之外使用的变量污染全局作用域。
为了这个目的,我们可以使用wrap选项来表示我们希望将生成的扭曲代码包装在一个包装函数中,如下面的示例所示:
uglify: {
main: {
src: 'src/main.js',
dest: 'dist/main.js',
options: {
wrap: true
}
}
}
如果我们现在查看结果dist/main.js文件,我们应该看到原始文件的所有扭曲内容现在都包含在一个包装函数中。
设置 RequireJS
在这个菜谱中,我们将使用contrib-requirejs (0.4.4)插件将我们的 Web 应用程序的模块化源代码打包到一个文件中。
在很大程度上,这个插件只是为RequireJS工具提供了一个包装器。RequireJS 提供了一个框架来模块化 JavaScript 源代码,并有序地消费这些模块。它还允许将整个应用程序打包到一个文件中,并只导入所需的模块,同时保持模块结构完整。
小贴士
要查看本配方中创建的捆绑应用程序的实际效果,请参阅为此配方提供的示例代码。它包括一个基本开发服务器设置,按照第一章中“设置基本 Web 服务器”配方进行设置,以及所需的库和一个消耗生成的应用程序捆绑文件的示例index.html文件。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
如何做到这一点...
以下步骤将指导我们创建一些用于示例应用程序的文件,并设置一个将它们捆绑成一个文件的任务。
-
我们将首先按照第一章中“安装插件”配方提供的说明安装包含
contrib-requirejs插件的包。 -
首先,我们需要一个包含我们的 RequireJS 配置的文件。让我们在
src目录下创建一个名为config.js的文件,并在其中添加以下内容:require.config({ baseUrl: 'app' }); -
其次,我们将创建一个我们希望在应用程序中使用的示例模块。让我们在
src/app目录下创建一个名为sample.js 的文件,并在其中添加以下内容:define(function (require) { return function () { console.log('Sample Module'); } }); -
最后,我们需要一个包含我们应用程序主入口点的文件,并使用我们的示例模块。让我们在
src/app目录下创建一个名为main.js的文件,并在其中添加以下内容:require(['sample'], function (sample) { sample(); }); -
现在我们已经拥有了构建示例应用程序所需的所有必要文件,我们可以设置一个
requirejs任务,将它们捆绑成一个文件:requirejs: { app: { options: { mainConfigFile: 'src/config.js', name: 'main', out: 'www/js/app.js' } } }小贴士
mainConfigFile选项指出将确定 RequireJS 行为的配置文件。name选项表示包含应用程序入口点的模块的名称。在本例中,我们的应用程序入口点包含在app/main.js文件中,而app是我们在src/config.js文件中的应用程序的基本目录。这把app/main.js文件名转换成了main模块名称。out选项用于指示应接收捆绑应用程序结果的文件。 -
我们现在可以使用
grunt requirejs命令运行任务,它应该产生类似于以下内容的输出:Running "requirejs:app" (requirejs) task -
现在,我们应该在
www/js目录下有一个名为app.js的文件,其中包含我们的整个示例应用程序。
更多内容...
requirejs任务为我们提供了由 RequireJS 工具提供的所有底层选项。我们将探讨如何使用这些公开的选项并生成源映射。
使用 RequireJS 优化器选项
RequireJS 优化器是一个非常复杂的工具,因此提供了大量的选项来调整其行为。contrib-requirejs 插件允许我们通过仅指定插件本身的选项来轻松设置这些选项。
小贴士
所有可用的 RequireJS 构建系统配置选项可以在以下 URL 的示例配置文件中找到:
github.com/jrburke/r.js/blob/master/build/example.build.js
以下示例表明,应该使用 optimize 选项来使用 UglifyJS2 优化器而不是默认的 UglifyJS 优化器:
requirejs: {
app: {
options: {
mainConfigFile: 'src/config.js',
name: 'main',
out: 'www/js/app.js',
optimize: 'uglify2'
}
}
}
生成源映射
当源代码捆绑成一个文件时,调试会变得有些困难,因为你现在必须浏览大量的代码才能到达你真正感兴趣的地方。
通过将生成的捆绑文件与其来源的模块化结构相关联,源映射可以帮助我们解决这个问题。简单来说,有了源映射,即使我们实际上使用的是捆绑文件,我们的调试器也会显示我们之前分开的文件。
以下示例使用了 generateSourceMap 选项来表明我们希望生成与结果文件一起的源映射:
requirejs: {
app: {
options: {
mainConfigFile: 'src/config.js',
name: 'main',
out: 'www/js/app.js',
optimize: 'uglify2',
preserveLicenseComments: false,
generateSourceMaps: true
}
}
}
小贴士
为了使用 generateSourceMap 选项,我们必须通过将 optimize 选项设置为 uglify2 来表明应该使用 UglifyJS2 进行优化,并且通过将 preserveLicenseComments 选项设置为 false 来表明不应保留许可证注释。
第七章. 部署到最终用户
在本章中,我们将介绍以下菜谱:
-
部署到 Rackspace Cloud Files
-
部署到 AWS S3
-
通过 FTP 部署
-
通过 SFTP 部署
-
部署到 GitHub Pages
-
使 AWS CloudFront 分发无效
-
通过 SSH 运行命令
简介
一旦我们的网络应用程序构建完成,并且其资产已优化以实现最佳交付和消费,就是时候将其提供给我们的目标受众了。这主要涉及将构成应用程序的资产传输到某种形式的文件托管系统,该系统专门用于通过互联网交付静态内容。
将网络应用程序的资产部署到互联网的主要关注点是可用性、速度和服务集成。资产应始终可以从世界任何地方访问,尽可能快地交付,并且托管系统应允许我们轻松上传和管理我们的内容。
部署到 Rackspace Cloud Files
在本菜谱中,我们将使用 cloudfiles (0.3.0) 插件将文件上传到 Rackspace Cloud Files 容器。
Cloud Files 服务默认提供一项附加好处,即为它托管的所有内容提供 内容分发网络 (CDN) 服务。
准备工作
在本例中,我们将使用我们在 第一章 中 在项目中安装 Grunt 菜单创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
以下示例还需要一个带有 API 密钥定义的 Rackspace Cloud 账户。我们还需要创建一个名为 myapp 的 Cloud Files 容器,并将其配置为托管一个 静态网站。
小贴士
请参考以下 URL 获取有关配置 Cloud Files 容器以托管静态网站的更多信息:
docs.rackspace.com/files/api/v1/cf-devguide/content/Create_Static_Website-dle4000.html
如何操作...
以下步骤将引导我们创建一个简单的 HTML 文档和一个将其上传到 Cloud Files 容器的任务:
-
我们将按照 第一章 中 安装插件 菜单提供的说明,安装包含
cloudfiles插件的包,使用 Grunt 入门。 -
接下来,我们将在项目目录中创建一个简单的 HTML 文档,名为
index.html,并为其提供以下内容:<html> <head> <title>MyApp</title> </head> <body> <h1>This is MyApp.</h1> </body> </html> -
现在,我们将向我们的配置中添加以下
cloudfiles任务,这将指示我们希望将index.html文件上传到myappCloud Files 容器:cloudfiles: { myapp: { user: '[username]', key: '[api key]', region: 'LON', upload: [{ container: 'myapp', src: 'index.html', }] } }小贴士
user和key选项填充了占位符值,以表明您应该使用您自己的 Rackspace Cloud 账户的用户名和 API 密钥。你可能希望将
user和key选项存储在本地文件中,而不是共享仓库中。请参考第一章中关于如何从外部文件导入配置的示例,即使用 Grunt 入门配方。同时,请确保排除包含访问凭证的项目代码库中的文件。region选项用于指示托管 Cloud Files 容器的地理位置区域。所需的区域在创建容器时指定。在我们的示例中,我们在LON区域创建了容器。 -
我们现在可以通过使用
grunt cloudfiles命令来运行任务,它应该会产生类似于以下内容的输出:Running "cloudfiles:myapp" (cloudfiles) task Uploading into myapp Syncing files to container: myapp Uploading index.html to myapp (NEW) -
我们现在可以确保文件已上传到我们的容器中,并且可以通过互联网访问。为此,我们需要从其设置中确定容器的目标域名,并在浏览器中导航到它。这应该看起来像以下这样:
![如何操作...]()
还有更多...
cloudfiles任务为我们提供了几个有用的选项,可以与基本上传功能一起使用。我们将查看如何将目录内容上传到目标目录。
上传目录内容
如果我们想上传目录内容,我们可以在src选项中使用 Grunt 支持的常规通配符模式。然而,我们可能还希望使用stripcomponents选项从目标文件的目录名称中删除前导路径。
以下示例将www目录的内容上传到myapp容器,并在确定目标文件名时从目标文件中删除第一个路径名:
cloudfiles: {
myapp: {
user: 'juriejan',
key: 'c980b9327b823b96dd83b51cdc5cf7dd',
region: 'LON',
upload: [{
container: 'myapp',
src: 'www/**/*',
stripcomponents: 1
}]
}
}
上传到目标目录
如果我们想将文件上传到目标容器上的特定目标目录,我们可以使用dest选项来指示目标目录,如下例所示:
cloudfiles: {
myapp: {
user: 'juriejan',
key: 'c980b9327b823b96dd83b51cdc5cf7dd',
region: 'LON',
upload: [{
container: 'myapp',
src: 'index.html',
dest: '/htmlfiles/'
}]
}
}
部署到 AWS S3
在此配方中,我们将使用aws-s3 (0.12.3)插件将文件上传到AWS S3 存储桶。
此服务默认不提供 CDN 设置,但可以轻松与AWS CloudFront集成,以加快托管文件的分发速度。
准备工作
在此示例中,我们将使用我们在第一章中在项目中安装 Grunt配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
除了标准的项目设置外,此配方还需要设置一个具有AWS 访问密钥的AWS 用户。
小贴士
有关如何获取您的 AWS 安全凭证的详细信息,请参阅以下 URL:
docs.aws.amazon.com/general/latest/gr/getting-aws-sec-creds.html
还需要创建一个以 [name].myapp 格式命名的存储桶,其中 [name] 是您希望使用的任何唯一名称。上述用户还应通过 AWS IAM 接口获得对创建的存储桶的完全访问权限。
小贴士
有关如何授予用户账户对 S3 存储桶的访问权限的信息,请参阅以下 URL:
为了使存储桶按预期工作,它还应配置为 静态网站托管,并且 index.html 应定义为它的 索引文档。
小贴士
有关如何配置 AWS S3 存储桶以进行静态网站托管的信息,请参阅以下 URL:
docs.aws.amazon.com/AmazonS3/latest/dev/HowDoIWebsiteConfiguration.html
如何操作...
以下步骤将引导我们创建一个示例 HTML 文档并配置一个任务,将文档上传到 AWS S3 存储桶:
-
我们将按照 第一章 中 安装插件 菜单提供的说明,安装包含
aws-s3插件的包,该菜单位于 使用 Grunt 入门。 -
接下来,我们将在项目目录中创建一个名为
index.html的简单 HTML 文档,并为其提供以下内容:<html> <head> <title>MyApp</title> </head> <body> <h1>This is MyApp.</h1> </body> </html> -
现在,我们将添加以下
aws_s3任务到我们的配置中,该任务表示我们希望将index.html文件上传到gruntbook.myapp存储桶:aws_s3: { myapp: { options: { accessKeyId: '[access key id]', secretAccessKey: '[secret access key]', region: 'eu-west-1', bucket: 'gruntbook.myapp', }, files: [{ src: 'index.html', dest: '/' }] } }小贴士
accessKeyId和secretAccessKey选项填充了占位符值,以表明您应使用自己的 AWS 访问凭证。accessKeyId选项应设置为为您的用户生成的 访问密钥 ID,该密钥 ID 是通过 AWS 身份和访问管理(IAM)控制台生成的。secretAccessKey选项应设置为为在accessKeyId选项中指定的特定访问密钥 ID 生成的 秘密访问密钥。请注意,秘密访问密钥仅在创建访问密钥 ID 时显示,因此如果您第一次没有保存它,您将无法在 IAM 控制台中找到它。您可能希望将
accessKeyId和secretAccessKey选项存储在本地文件中,而不是共享存储库中。请参阅 第一章 的 导入外部数据 菜谱,开始使用 Grunt,了解如何从外部文件导入配置的示例。此外,请确保将包含访问凭证的文件从您的项目代码存储库中排除。region选项用于指示托管存储桶的地理位置。所需的区域在创建存储桶时指定。在我们的示例中,我们在eu-west-1区域创建了一个容器。注意,此任务的文件配置支持所有标准的 Grunt 选项。您可以在以下网址了解更多信息:
-
我们现在可以通过使用
grunt aws_s3命令来运行任务,这应该会产生类似于以下输出的结果:Running "aws_s3:myapp" (aws_s3) task Uploading to https://s3-eu-west-1.amazonaws.com/gruntbook.myapp/ . List: (1 objects): - index.html -> https://s3-eu-west-1.amazonaws.com/gruntbook.myapp/index.html -
现在,我们可以确保文件已上传到我们的容器中,并且可以通过互联网访问。为此,我们需要从其属性部分确定存储桶的端点,并在浏览器中导航到它。这应该看起来像以下这样:
![如何操作...]()
还有更多...
aws_s3 任务为我们提供了一些有用的选项,可以与它的上传功能一起使用。我们将探讨如何指定上传文件的访问权限并启用并发操作。
指定上传文件的访问权限
每个上传到 AWS S3 存储桶的文件都有一组访问权限,指示谁可以访问它。如果我们想指定一组特定的权限,可以通过使用 access 选项来实现。以下示例将 access 选项设置为 private,表示所有上传的文件只能由上传过程中使用的用户账户访问:
aws_s3: {
myapp: {
options: {
accessKeyId: 'AKIAJG27ICB3NTY3SGCQ',
secretAccessKey: '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',
region: 'eu-west-1',
bucket: 'gruntbook.myapp',
access: 'private'
},
files: [{
src: 'index.html',
dest: '/'
}]
}
}
小贴士
访问选项的可能值列表可以在以下 AWS putObject 操作的文档中找到(在 ACL 参数下查看):
docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
启用并发上传
aws_s3 任务的默认行为是依次完成上传。如果我们想并行执行上传,可以通过使用 uploadConcurrency 选项来实现。在以下示例中,我们表明我们希望同时上传的最大文件数为 3:
aws_s3: {
myapp: {
options: {
accessKeyId: 'AKIAJG27ICB3NTY3SGCQ',
secretAccessKey: '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',
region: 'eu-west-1',
bucket: 'gruntbook.myapp',
uploadConcurrency: 3
},
files: [{
expand: true,
cwd: 'www',
src: '**/*',
dest: '/'
}]
}
}
小贴士
之前的示例还演示了递归上传整个目录内容的配置。
通过 FTP 部署
在本菜谱中,我们将使用 ftp-push (0.3.2) 插件将文件上传到托管服务器,使用 文件传输协议 (FTP)。
FTP 自互联网早期以来就存在,并且仍然被广泛使用。正如其名称所暗示的,它提供了一种在互联网上传输文件的方式,因此,自其诞生以来,它一直是部署资源到 Web 服务器的基石。
准备工作
在本例中,我们将使用我们在第一章中创建的基本项目结构,即在项目中安装 Grunt 配方中创建的结构。如果您还不熟悉其内容,请务必参考它。
除了标准的项目设置外,以下配方还需要在目标 FTP 启用服务器上现有的用户账户。他们的凭证通常由托管服务提供商或负责维护相关服务器的系统管理员提供。
如何操作...
以下步骤将引导我们创建一个简单的 HTML 文档,并配置一个任务,使用 FTP 将其上传到服务器:
-
我们将按照第一章中安装插件配方中提供的说明,安装包含
ftp-push插件的包。 -
接下来,我们将在项目目录中创建一个简单的 HTML 文档,命名为
index.html,并为其提供以下内容:<html> <head> <title>MyApp</title> </head> <body> <h1>This is MyApp.</h1> </body> </html> -
现在,我们将添加以下
ftp_push任务到我们的配置中,并指出我们希望它将index.html文件上传到我们的托管服务器:ftp_push: { myapp: { options: { host: '[host]', username: '[username]', password: '[password]', dest: '/www' }, files: [{ src: 'index.html', }] } }小贴士
username、password和host选项填写了占位符值,以表明您应该使用自己的特定访问凭证和托管服务器地址。您可能希望将
username和password选项存储在本地文件中,而不是共享存储库中。请参考第一章中导入外部数据配方中的示例,了解如何从外部文件导入配置。同时,请注意,您应该从项目的代码存储库中排除包含访问凭证的文件。host选项用于指定您想要上传文件的互联网地址。如果您不确定这个值应该是多少,此类信息通常可以由托管提供商或负责维护相关服务器的系统管理员提供。dest选项是必需的,用于指示我们将上传的文件的目标目录。在我们的例子中,我们将指示文件应该上传到托管服务器上的www目录。注意,此任务的文件配置支持所有标准的 Grunt 选项。您可以在以下 URL 中了解更多信息:
-
我们现在可以通过使用
grunt ftp_push命令来运行此任务,它应该产生类似于以下内容的输出:Running "ftp_push:myapp" (ftp_push) task >> [username] successfully authenticated! >> /www/index.html transferred successfully. >> FTP connection closed!小贴士
注意,
ftp_push任务不能自动创建目标目录,因此在运行此任务之前,FTP 服务器的根目录上需要存在www目录。 -
我们的服务器上现在应该已经有了
index.html文件,并且如果配置正确,应该可以通过互联网访问。将域名配置为指向以这种方式托管站点的操作超出了本书的范围。
通过 SFTP 部署
在本菜谱中,我们将使用由 ssh (0.12.2) 插件提供的 sftp 任务,通过 SSH 文件传输协议(SFTP)将文件上传到托管服务器。
SFTP 提供了与之前菜谱中讨论的常规 FTP 相同的功能,但通过利用 安全壳(SSH)网络协议的附加安全层提供了额外的安全优势。
准备工作
在本例中,我们将使用我们在 第一章 中创建的基本项目结构,即 在项目中安装 Grunt 菜谱,使用 Grunt 开始。如果您还不熟悉其内容,请务必参考它。
除了标准项目设置外,以下菜谱还需要在目标启用了 SSH 的服务器上现有的用户账户。这些凭证通常由托管服务提供商或负责维护目标服务器的系统管理员提供。
如何操作...
以下步骤将引导我们创建一个简单的 HTML 文档,并配置一个任务,使用 SFTP 将其上传到服务器:
-
我们将按照
Installing a plugin菜谱中提供的说明安装包含ssh插件的包。 -
接下来,我们将在项目目录中创建一个名为
index.html的简单 HTML 文档,并为其提供以下内容:<html> <head> <title>MyApp</title> </head> <body> <h1>This is MyApp.</h1> </body> </html> -
现在,我们将添加以下
sftp任务到我们的配置中,这表明我们希望将index.html文件上传到支持 SFTP 的特定主机:sftp: { myapp: { options: { host: '[host]', username: '[username]', password: '[password]', path: 'www/' }, files: [{ src: 'index.html', }] } }小贴士
username、password和host选项填充了占位符值,以表明您应该使用自己的特定访问凭证和托管服务器地址。您可能希望将
username和password选项存储在本地文件中,而不是共享仓库中。请参考 第一章 中 导入外部数据 菜谱的示例,了解如何从外部文件导入配置。同时,请记住,您应该从项目的代码仓库中排除包含访问凭证的文件。host选项用于指定您想要上传文件的服务器的互联网地址。如果您不确定这个值应该是多少,此类信息通常可以由托管提供商或负责维护相关服务器的系统管理员提供。path选项是必需的,用于指定我们将上传的文件的目标目录。在我们的例子中,我们将指示文件应该上传到托管服务器上的www目录。注意,此任务的文件配置支持所有标准 Grunt 选项。您可以在以下网址了解更多信息:
-
我们现在可以通过使用
grunt sftp命令来运行任务,它应该产生类似于以下内容的输出:Running "sftp:myapp" (sftp) task Copied 1 files小贴士
注意,
sftp任务不能自动创建目标目录,因此在运行此任务之前,www目录需要存在于 FTP 服务器的根目录。 -
我们的
index.html文件现在应该出现在我们的托管服务器上,如果配置正确,应该可以通过互联网访问。配置域名指向以这种方式托管站点超出了本书的范围。
更多...
sftp 任务为我们提供了几个有用的选项,可以与它的上传功能一起使用。我们将探讨如何使用私钥和密码短语以及使用 SSH 代理。
使用私钥和密码短语
如果我们想使用私钥和密码短语来访问我们的主机服务器,我们可以通过使用 privateKey 和 passphrase 选项来实现。在下面的例子中,我们将使用 grunt.file.load 函数从常规位置加载我们的私钥,并提供用于锁定它的密码短语。
sftp: {
myapp: {
options: {
host: '[host]',
username: '[username]',
privateKey: grunt.file.read('[path to home]/.ssh/id_rsa'),
passphrase: '[passphrase]',
path: 'www/'
},
files: [{
src: 'index.html',
}]
}
}
使用 SSH 代理
如果我们经常使用 SSH 访问我们的服务器,我们可能最好在会话期间第一次解锁后使用 SSH 代理 来存储我们的未加密私钥。这将允许我们访问所有使用我们的 公钥 的服务,而无需在整个用户会话期间再次输入密码短语。
小贴士
大多数类 Unix(包括 OS X)操作系统应该默认安装并运行 SSH 代理,在这种情况下,以下示例应该无需任何初始步骤即可工作。Windows 用户将需要手动安装和配置 SSH 代理。
以下示例通过使用 agent 选项指定套接字文件路径来使用 SSH 代理:
sftp: {
myapp: {
options: {
host: '[host]',
username: '[username]',
agent: process.env.SSH_AUTH_SOCK,
path: 'www/'
},
files: [{
src: 'index.html',
}]
}
}
小贴士
大多数操作系统捆绑的 SSH 代理程序遵循使用 SSH_AUTH_SOCK 环境变量提供其运行套接字的约定。在我们的例子中,我们使用标准的 Node.js process.env 对象来检索此环境变量的值。
部署到 GitHub Pages
在这个配方中,我们将使用gh-pages (0.10.0)插件将我们的站点发布到GitHub Pages服务。
GitHub Pages 服务为 GitHub 用户提供了一种简单的方式来托管与他们自己、他们的组织或他们的项目相关的静态站点。该服务的核心是提供基于Git代码仓库托管的GitHub标准服务。
准备工作
在这个例子中,我们将使用我们在第一章中创建的基本项目结构,即在项目中安装 Grunt配方中的使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
除了标准的项目设置外,以下配方还需要一个具有适当公钥(SSH)关联的 GitHub 用户账户。
小贴士
请参考以下 URL 以获取有关为您的 GitHub 账户创建和设置 SSH 密钥的帮助:
help.github.com/articles/generating-ssh-keys/
我们还需要在 GitHub 上创建一个名为myapp的仓库,克隆它,并将其用作我们的项目文件夹。
小贴士
您可以在以下 URL 中了解更多关于如何在 GitHub 上创建和克隆仓库的信息:
help.github.com/articles/creating-a-new-repository/
help.github.com/articles/cloning-a-repository/
如何操作...
以下步骤将指导我们创建一个简单的 HTML 文档,并配置一个将其发布到 GitHub Pages 站点上的任务:
-
我们将按照第一章中安装插件配方提供的说明来安装包含
gh-pages插件的包。 -
接下来,我们将在
www目录下创建一个简单的 HTML 文档,名为index.html,并为其提供以下内容:<html> <head> <title>MyApp Project Site</title> </head> <body> <h1>Welcome!</h1> <h2>This is the MyApp project site.</h1> </body> </html> -
现在,我们将添加以下
gh-pages任务到我们的配置中,并指出我们希望将www目录的内容上传到我们的项目仓库:'gh-pages': { myapp: { options: { base: 'www' }, src: '**/*' } }小贴士
base选项用于提供包含我们站点的目录。在我们的例子中,我们将使用www目录,因为这是我们创建示例 HTML 文件的地方。src选项用于提供应传输到目标仓库的基于基本目录的文件。在我们的例子中,我们将其设置为**/*,表示我们希望将基本目录中的所有文件传输。 -
我们现在可以通过使用
grunt gh-pages命令来运行任务,它应该产生类似于以下内容的输出:Running "gh-pages:myapp" (gh-pages) task Cloning git@github.com:[name]/myapp.git into .grunt/grunt-gh-pages/gh-pages/myapp Cleaning Fetching origin Checking out origin/gh-pages Removing files Copying files Adding all Committing Pushing -
现在,我们可以通过导航到 GitHub 自动分配给我们的项目的
[name].github.io/myapp域名来查看我们的项目站点。这样做应该看起来像以下这样:![如何操作...]()
使 AWS CloudFront 分发失效
在此配方中,我们使用invalidate-cloudfront (0.1.6)插件来使 AWS CloudFront 分发失效。
AWS CloudFront 服务为我们提供了一个简单的方法,通过全球边缘位置来分发我们的网站和应用程序的文件。这导致我们的目标受众无论在世界任何地方都能获得更快的响应时间。
将我们的文件托管在 CDN 上的副作用是,每当它们在源处更新时,更新可能需要一段时间才能反映在各个边缘位置,这可能会阻止我们的观众获取关键更新。然而,AWS CloudFront 确实允许我们通过使分发失效来指示我们希望刷新存储在 CDN 上的内容。
准备工作
在本例中,我们将使用我们在第一章“使用 Grunt 入门”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考。
除了标准的项目设置外,此配方还需要设置一个具有 AWS 访问密钥的 AWS 用户。
小贴士
有关如何获取您的 AWS 访问凭证的详细信息,请参阅以下 URL:
docs.aws.amazon.com/general/latest/gr/getting-aws-sec-creds.html
还需要创建一个 AWS CloudFront 分发,以便我们可以在配方中使用它。在我们的示例中,我们将使其分发我们在本章前面“部署到 AWS S3”配方中创建的存储桶中的文件。
小贴士
有关如何创建 AWS CloudFront 分发的更多信息,请参阅以下 URL:
docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-web-creating.html
如何操作...
以下步骤将指导我们创建和配置一个使特定 AWS CloudFront 分发失效的任务:
-
我们将按照第一章“使用 Grunt 入门”配方中提供的说明,安装包含
invalidate-cloudfront插件的包。 -
现在,我们将添加以下
invalidate_cloudfront任务到我们的配置中,并指示我们希望刷新在www目录中可以找到的每个文件:invalidate_cloudfront: { myapp: { options: { key: '[key]', secret: '[secret]', distribution: '[distribution id]' }, files: [{ expand: true, cwd: 'www', src: '**/*' }] } }小贴士
key和secret选项填充了占位符值,以表明您应该使用自己的特定访问凭证。key选项应设置为为您的用户生成的访问密钥 ID,使用 AWS IAM 控制台。secret选项应设置为为在key选项中指定的特定访问密钥 ID 生成的秘密访问密钥。请注意,秘密访问密钥仅在创建访问密钥 ID 时显示,因此如果您第一次没有保存它,您将无法在 IAM 控制台中找到它。您可能希望将
key和secret选项存储在本地文件中,而不是共享存储库中。请参考第一章中“导入外部数据”配方,了解如何从外部文件导入配置的示例。同时,请注意,您应该从项目的代码存储库中排除包含安全凭证的文件。 -
我们现在可以通过使用
grunt invalidate_cloudfront命令来运行任务,该命令应该产生类似于以下内容的输出:Running "invalidate_cloudfront:myapp" (invalidate_cloudfront) task Invalidating 1 files: /index.html 0 Completed and 0 In Progress invalidations on: [dist id] Creating invalidation for 1 files Invalidation created at https://cloudfront.amazonaws.com/2014-10-21/distribution/[dist id]/invalidation/[invalidation id] -
目标 CloudFront 分发现在应该正在被使无效。如果对底层内容进行了任何更改,它应该在 CloudFront CDN 的所有边缘位置上反映出来,这通常需要几分钟。
通过 SSH 运行命令
在本配方中,我们将使用sshexec任务,该任务由ssh (0.12.2)插件提供,通过 SSH 网络协议在远程服务器上运行命令。
在部署过程中,在远程服务器上运行命令可能成为必需,当我们运行命令时,我们希望确保它是安全进行的。SSH 协议是运行远程服务器命令的事实标准,部分原因是它通过加密通过网络发送的所有数据来提供改进的安全性。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
除了标准项目设置外,以下配方还需要在目标启用了 SSH 的服务器上现有的用户账户。这些凭据通常由托管服务或维护目标服务器的管理员提供。
如何操作...
以下步骤将指导我们创建和配置一个任务,该任务将运行一个在远程服务器上打印日期和时间的命令:
-
我们将按照第一章中“安装插件”配方中提供的说明来安装包含
ssh插件的包。 -
现在,我们将以下
sshexec任务添加到我们的配置中,并设置它通过运行date命令在远程服务器上打印日期和时间:sshexec: { date: { options: { host: '[host]', username: '[username]', password: '[password]' }, command: 'date' } }小贴士
username、password和host选项填充了占位符值,以表明您应该使用您自己的特定访问凭证和托管服务器。您可能希望将
username和password选项存储在本地文件中,而不是共享存储库中。有关如何从外部文件导入配置的示例,请参阅第一章中的 导入外部数据 菜谱,使用 Grunt 入门。同时,请记住,您应该从项目的代码存储库中排除包含访问凭证的文件。使用
host选项来指定您想要上传文件的服务器的 Internet 地址。如果您不确定这个值应该是多少,此类信息通常可以由托管提供商或负责维护相关服务器的系统管理员提供。 -
我们现在可以通过使用
grunt sshexec命令来运行此任务,它应该产生类似于以下内容的输出:Running "sshexec:myapp" (sshexec) task Thu Jan 1 12:00:00 GMT 2015 -
如果我们在运行命令后看到返回的日期和时间,那么我们已经成功在远程服务器上运行了命令。
还有更多...
sshexec 任务为我们提供了几个有用的选项,这些选项可以与其上传功能一起使用。我们将探讨如何使用私钥和密码短语以及使用 SSH 代理。
使用私钥和密码短语
如果我们想使用私钥和密码短语来访问我们的主机服务器,我们可以通过使用 privateKey 和 passphrase 选项来实现。在以下示例中,我们将使用 grunt.file.load 函数从常规位置加载我们的私钥,并提供用于锁定它的密码短语。
sshexec: {
date: {
options: {
host: '[host]',
username: '[username]',
privateKey: grunt.file.read('[path to home]/.ssh/id_rsa'),
passphrase: '[passphrase]'
},
command: 'date'
}
}
使用 SSH 代理
如果我们经常使用 SSH 访问我们的服务器,我们最好在会话期间第一次解锁后使用 SSH 代理来存储我们的未加密私钥。这将允许我们访问所有使用我们的公钥的服务,而无需在整个用户会话期间再次输入密码短语。
小贴士
大多数 *nix(这包括 OS X)操作系统应该默认安装并运行 SSH 代理,在这种情况下,以下示例应该无需任何初始步骤即可工作。Windows 用户将需要手动安装和配置 SSH 代理。
以下示例通过指定带有 agent 选项的套接字来使用 SSH 代理:
sshexec: {
date: {
options: {
host: '[host]',
username: '[username]',
agent: process.env.SSH_AUTH_SOCK
},
command: 'date'
}
}
小贴士
大多数操作系统捆绑的 SSH 代理程序遵循使用 SSH_AUTH_SOCK 环境变量提供其运行套接字的约定。在我们的示例中,我们使用了标准的 Node.js process.env 对象来检索此环境变量的值。
第八章。创建自定义任务
在本章中,我们将涵盖以下配方:
-
创建别名任务
-
创建基本任务
-
访问项目配置
-
检查所需配置
-
检查其他任务的执行是否成功
-
在任务中运行非阻塞代码
-
任务失败
-
使用命令行参数
-
将任务入队以运行
-
创建多任务
-
在任务中使用选项
-
在任务中使用文件
简介
要真正开始欣赏 Grunt 的强大和灵活性,我们必须深入研究创建我们自己的任务。
在我们可用的各种插件中,每个插件提供的任务都可以以多种方式配置,应该能够满足我们的大部分需求。然而,如果我们遇到无法使用现有任务和配置组合解决的问题,我们可以轻松地创建一个新的任务来填补这个空白。
我们也应该将创建任务视为创建我们自己的插件的第一步。本章中讨论的创建任务的方法将为我们提供一个简单的实验室,我们可以在这里构建最终可以直接转移到插件中的任务。
小贴士
以下两个 URL 提供了有关创建任务和 Grunt 提供的构建它们的工具的大量信息。如果您认真考虑创建任务,强烈建议您查看这些链接:
创建别名任务
我们为项目配置的任务往往只执行一个功能,没有更多。随着项目的增长,我们可能会开始识别出我们倾向于按特定顺序一起运行的多个任务组。在这个时候,别名任务可以对我们非常有帮助,因为它允许我们将一组任务组合成一个新任务名下。
准备工作
在这个例子中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构。如果您还不熟悉其内容,请务必参考。
这个配方还包含一个示例,它使用了第一章中“在项目中安装 Grunt”配方中的设置基本 Web 服务器和监视文件变化配方,以及第三章中“模板引擎”配方中的渲染 Jade 模板配方,在一个设置中。如果您想深入了解这些方面,请务必参考这些内容。
如何操作...
以下步骤将引导我们构建一个基本的网站开发设置,该设置会持续从模板渲染 HTML 文件,使用基本网络服务器提供服务,并在开始之前通过删除所有先前渲染的模板来确保干净的环境:
-
我们将首先按照 第一章 的 安装插件 菜谱中提供的说明安装包含
contrib-clean、contrib-connect、contrib-jade和contrib-watch插件的包。 -
接下来,我们将在
templates目录中创建一个简单的 Jade 模板,名为index.jade,并为其提供以下内容:doctype html html head title Sample Site body h1 Welcome to my Sample Site! -
现在,我们可以添加任务来监视、渲染、提供服务和清理我们的配置:
clean: { all: ['www'] }, jade: { www: { expand: true, cwd: 'templates', src: '**/*.jade', dest: 'www', ext: '.html' } }, connect: { dev: { options: { base: 'www' } } }, watch: { www: { files: 'templates/**/*.jade', tasks: ['jade:www'] } } -
在所有任务就绪之后,我们现在可以创建一个名为
run的别名任务,它将它们按适当的顺序全部关联起来。这是通过将以下内容添加到我们的 Grunt 配置文件中的主函数末尾来完成的:grunt.registerTask('run', [ 'clean:all', 'jade:www', 'connect:dev', 'watch:www' ]); -
现在,我们可以通过使用
grunt run命令来运行别名任务,就像运行常规任务一样,这应该会产生类似于以下内容的输出:Running "clean:all" (clean) task >> 0 paths cleaned. Running "jade:www" (jade) task >> 1 file created. Running "connect:dev" (connect) task Started connect web server on http://localhost:8000 Running "watch" task Waiting… -
如我们从输出中看到的那样,我们配置的任务都按照
run任务中指定的顺序运行,我们现在正在提供渲染后的index.jade模板。为了测试设置,我们可以在浏览器中导航到http://localhost:8000,这将显示渲染后的模板。
还有更多...
可能会有这样的情况,我们有一个特定的任务,我们希望比其他任务运行得多得多,以至于我们可能会把它视为默认任务。在这种情况下,我们可以使用 default 任务别名。当没有与 grunt 命令一起指定的特定任务时,将调用此别名。
我们可以通过更改别名任务声明为以下内容来修改我们的主菜谱以使用 default 任务:
grunt.registerTask('default', [
'clean:all',
'jade:www',
'connect:dev',
'watch:www'
]);
如果我们现在在我们的项目中运行 grunt 命令而不跟随着任务名称,它应该以与上一节中相同的方式运行所有任务。
创建基本任务
尽管 Grunt 用户有大量的插件可用,但仍然可能发生我们想要创建自己的任务的情况。Grunt 提供了很好的支持,因为它提供了一套使创建新任务变得相当简单的实用工具。
准备工作
在本例中,我们将使用我们在 第一章 的 在项目中安装 Grunt 菜谱中创建的基本项目结构,开始使用 Grunt。如果您还不熟悉其内容,请务必参考它。
如何做到这一点...
以下步骤将引导我们创建一个简单的任务,该任务会打印当前系统日期和时间。
-
首先,我们将使用名称
datetime注册我们的任务,并为其提供描述和一个空函数作为占位符。以下代码显示了带有任务注册代码高亮的整个 Grunt 文件:module.exports = function (grunt) { grunt.initConfig({}); grunt.registerTask('default', []); grunt.registerTask( 'datetime', 'Prints out the current date and time.', function () { } ); }; -
在任务注册后,我们现在可以提供代码,该代码将实际打印当前日期和时间。以下代码显示了带有新添加代码高亮的任务注册:
grunt.registerTask( 'datetime', 'Prints out the current date and time.', function () { var date = new Date(); grunt.log.writeln(date.toString()); } );小贴士
这段代码使用了标准的 JavaScript
Date对象和 Grunt 的grunt.log工具,该工具专门为任务提供,用于记录所有类型的消息。您可以在以下网址了解更多关于
grunt.log工具的信息: -
现在,我们可以通过运行
grunt datetime命令来尝试运行新创建的任务,它应该产生类似于以下内容的输出:Running "datetime" task Thu Jan 1 2015 12:00:00 GMT -
如果任务的输出显示了当前系统日期和时间,那么我们就成功创建并执行了我们的简单自定义任务。
访问项目配置
为了使任务在项目中正常工作,它通常需要一种方式来获取有关它的特定细节。Grunt 项目的标准做法是在 grunt.initConfig 函数提供的配置对象中提供项目特定的细节。
准备工作
在这个例子中,我们将使用我们在 第一章 的 在项目中安装 Grunt 配方中创建的基本项目结构,使用 Grunt 入门。如果您还不熟悉其内容,请务必参考。
如果这个配方中的任何步骤看起来难以理解,请务必查看本章前面提供的 创建基本任务 配方。
如何操作...
以下步骤将引导我们创建一个打印出由项目配置确定的消息的任务。
-
首先,我们将使用
grunt.initConfig函数添加一些示例项目配置。以下代码显示了带有配置高亮的整个 Grunt 文件内容:module.exports = function (grunt) { grunt.initConfig({ location: 'Earth', datetime: '<%= new Date().toString() %>', project: { name: 'Life' } }); grunt.registerTask('default', []); };小贴士
注意在
datetime配置中使用模板字符串。以下网址提供了有关在配置值中使用模板字符串的更多信息: -
接下来,我们将使用名称
describe注册我们的任务,并为其提供描述和一个空函数作为占位符:grunt.registerTask( 'describe', 'Describes the situation.', function () { } ) -
在任务注册后,我们可以用以下代码填充空函数,该代码检索一些项目配置并在打印我们的消息时使用它:
var projectName = grunt.config('project.name'); var location = grunt.config('location'); var datetime = grunt.config('datetime'); grunt.log.write( projectName + ' on ' + location + ' at ' + datetime );小贴士
在这里,我们使用
grunt.config函数从grunt.initConfig函数提供的配置对象中获取数据。注意,在从配置对象中的对象检索属性时可以使用点符号,如
projectName变量所示。使用
datetime变量演示的另一个概念是,使用grunt.config函数检索的值是渲染过的,这意味着任何包含模板的字符串都将被解析和渲染。如果出于某种原因我们想要获取未渲染的值,我们可以通过使用grunt.config.getRaw函数来实现。 -
现在,我们可以通过运行
grunt describe命令来尝试我们的自定义任务,应该会产生类似于以下输出的结果:Running "describe" task Life on Earth at Thu Jan 1 2015 12:00:00 GMT -
如果任务的输出显示了一个包含我们在项目配置中提供的值的消息,那么我们已经成功创建并执行了一个利用项目配置的任务。
检查所需配置
对于任务来说,通常需要一组最小配置才能正常工作是很常见的。Grunt 框架专门为此提供了 grunt.config.requires 函数,如果找不到指定的配置,则会失败任务。
准备工作
在这个例子中,我们将使用在 第一章 中 在项目中安装 Grunt 食谱中创建的基本项目结构,开始使用 Grunt。如果你还不熟悉其内容,请务必参考。
如果这个食谱中的任何步骤看起来难以理解,请务必查看本章前面提供的 创建基本任务 食谱。
如何操作...
以下步骤将引导我们创建两个任务:第一个将检查我们提供的所需配置,第二个将检查我们未提供的配置。
-
首先,我们将使用
grunt.initConfig函数添加一些示例项目配置。以下代码显示了包含配置高亮的整个 Grunt 文件的内容:module.exports = function (grunt) { grunt.initConfig({ integral: 'check', complex: { important: 'present' } }); }; -
接下来,我们将在
grunt.initConfig调用之后添加以下代码,以注册一个名为complete的任务,该任务检查我们提供的配置:grunt.registerTask( 'complete', 'Complete Configuration.', function () { grunt.config.requires('integral'); grunt.config.requires('complex.important'); grunt.log.write('Complete: Success!'); } ); -
然后,我们可以通过添加以下代码来注册一个名为
incomplete的任务,该任务检查我们未提供的配置:grunt.registerTask( 'incomplete', 'Incomplete Configuration.', function () { grunt.config.requires('missing'); grunt.log.write('Incomplete: Success!'); } ); -
在添加了我们的任务之后,我们首先将运行一个成功的任务,使用
grunt complete命令,应该会产生类似于以下输出的结果:Running "complete" task Complete: Success! -
然后,我们可以使用
grunt incomplete命令运行应该失败的任务,应该会产生类似于以下输出的结果:Running "incomplete" task Verifying property missing exists in config...ERROR >> Unable to process task. Aborted due to warnings. -
如我们从最后一个任务的输出中看到的那样,它由于找不到
missing配置属性而失败。
检查其他任务的执行成功
由于任务相互依赖会损害其可重用性,因此任务之间相互依赖的情况并不常见。如果任务紧密耦合到无法独立运行,应强烈考虑将它们合并为一个单独的任务。
话虽如此,总有例外,Grunt 通过grunt.task.requires函数提供了对此的处理。当调用此函数时,它会检查指定名称的任务是否已成功执行。如果它发现情况并非如此,它将使当前任务失败。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即“使用 Grunt 入门”。如果你还不熟悉其内容,请务必参考。
如果本配方中的任何步骤看起来难以理解,请务必查看本章前面提供的“创建基本任务”配方。
如何操作...
以下步骤将引导我们创建两个任务。第一个任务将打印一条简单的消息,第二个任务将检查第一个任务是否已成功执行。
-
首先,我们将注册一个名为
independent的任务,该任务可以在没有运行任何任务的情况下运行,并在完成时打印一条消息。这是通过将以下代码添加到我们的 Grunt 文件中的主函数中实现的:grunt.registerTask( 'independent', 'Independent task.', function () { grunt.log.write('Independent: Success!') } ); -
接下来,我们将注册一个名为
dependent的任务,如果independent任务没有成功运行,它将会失败。这是通过将以下代码添加到我们的 Grunt 文件中的主函数中实现的:grunt.registerTask( 'dependent', 'Dependent task.', function () { grunt.task.requires('independent'); grunt.log.write('Dependent: Success!') } ); -
为了测试这个设置,我们可以首先使用
grunt dependent命令运行dependent任务,这应该会产生类似于以下内容的输出:Running "dependent" task Warning: Required task "independent" must be run first. Use --force to continue. Aborted due to warnings. -
从输出中我们可以看到,这个任务失败了,因为在尝试运行“依赖”任务之前,“独立”任务并没有成功执行。
-
最后,我们可以使用
grunt independent dependent命令连续运行我们创建的两个任务,这应该会产生类似于以下内容的输出:Running "independent" task Independent: Success! Running "dependent" task Dependent: Success! Done, without errors. -
这次我们可以看到,这两个任务都运行得很好,没有遇到任何问题,并打印了它们各自的消息。
在任务中运行非阻塞代码
在 Node.js 领域可用的大多数库通常都是按照非阻塞的方式实现的。这意味着调用库提供的函数后,通常会继续执行被调用之后的下一行代码,即使它还没有完成其预期的目的。
这些类型的函数通常会使用事件驱动方法或回调函数来指示它们的进度。在这两种情况下,我们都需要一个在任务完成后可以调用的函数,为此,Grunt 为我们提供了this.async实用工具。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即“使用 Grunt 入门”。如果你还不熟悉其内容,请务必参考。
如果本菜谱中的任何步骤看起来难以理解,请务必查看本章前面提供的 创建基本任务 菜谱。
如何操作...
以下步骤将指导我们创建一个任务,该任务每秒打印当前系统时间 5 秒,然后退出。
-
首先,我们将通过在 Grunt 文件中的主函数末尾添加以下代码来注册一个名为
clock的任务,包括一个描述和一个空的占位符函数:grunt.registerTask( 'clock', 'Prints the time every second for 5 seconds.', function () { } ); -
接下来,我们将在我们的占位符函数内部定义一些变量,这些变量将用于我们的时间打印代码:
var done = this.async(); var count = 5; var interval = null;小贴士
done变量是本菜谱的重点,因为它展示了this.async函数的使用。它被分配给调用this.async函数返回的函数。这个返回的函数可以被调用以指示任务是否已完成。count变量将用于跟踪系统时间打印的次数。每次打印日期和时间时,它都会递减,并且当它达到0的值时,我们将停止打印时间。interval变量用于存储在任务函数末尾启动的 计时器间隔 的 ID。我们需要这个 ID 来在count变量达到0的值时停止计时器间隔的运行。 -
在所有必要的变量都已就绪后,我们现在可以定义一个函数,该函数将打印当前系统日期和时间,并在运行完毕后停止计时器。以下代码应该在之前的变量定义之后立即添加:
printTime = function () { grunt.log.writeln(new Date()); count -= 1; if (count <= 0) { clearInterval(interval); done(); } };小贴士
在这里,我们使用
grunt.log工具通过提供一个新创建的内置 JavaScriptDate对象来打印当前系统日期和时间,该对象默认将始终包含当前日期和时间。我们还递减了计数变量,并检查它是否小于或等于
zero。如果我们发现它小于或等于零,我们将使用内置的clearInterval函数来停止计时器间隔的运行。我们将在下一步将计时器间隔 ID 分配给interval变量。一旦我们看到计数器达到
0并且我们已经停止了时间间隔,我们可以使用this.async函数返回给我们的done函数来指示任务已运行完成。 -
最后,我们将在
printTime函数定义之后立即添加以下代码来启动一个每1000毫秒运行一次之前步骤中定义的函数的计时器间隔:interval = setInterval(printTime, 1000); -
在我们的任务中包含所有必要的代码后,我们可以通过运行
grunt clock命令来尝试它,它应该产生类似于以下内容的输出:Running "clock" task Thu Apr 1 2015 12:00:00 GMT Thu Apr 1 2015 12:00:01 GMT Thu Apr 1 2015 12:00:02 GMT Thu Apr 1 2015 12:00:03 GMT Thu Apr 1 2015 12:00:04 GMT Done, without errors. -
如任务输出所示,它已打印当前系统日期和时间五次,然后停止。
任务失败
任务的成功或失败可以用来指示任务是否无问题完成,或者是否在执行其预期功能之前遇到了问题。默认情况下,Grunt 会假设任务已成功,但它也提供了一个简单的方法来指示任务是否失败以及传递其失败的原因。
准备工作
在本例中,我们将使用我们在第一章中“在项目中安装 Grunt”配方中创建的基本项目结构,即开始使用 Grunt。如果您还不熟悉其内容,请务必参考。
如果本配方中的任何步骤看起来难以理解,请务必查看本章前面提供的访问项目配置配方。
如何实现...
以下步骤将引导我们创建一个任务,其成功基于项目配置属性的值:
-
我们将首先通过在 Grunt 文件中的主函数末尾添加以下代码来注册一个名为
check的任务,并带有描述和空占位符函数:grunt.registerTask( 'check', 'Fails if indicated in configuration.', function () { } ); -
任务注册后,我们现在可以填充空函数,以检索项目配置属性并使用它来决定是否使任务失败:
var fail = grunt.config('shouldFail'); if (fail === true) { return new Error('Error Message.'); } else { grunt.log.writeln('Success!'); }小贴士
注意,我们返回一个包含消息的新创建的
Error对象,以指示任务已失败。如果我们只想使任务失败而不提供任何消息,我们只需返回false值即可。 -
在我们的任务准备就绪后,我们可以添加所需的配置属性以指示它不应失败。以下代码显示了带有新配置高亮的整个
grunt.initConfig调用:grunt.initConfig({ shouldFail: false }); -
现在,我们可以运行任务以查看它是否按预期成功。这可以通过使用
grunt check命令来完成,它应该产生类似于以下内容的输出:Running "check" task Success! Done, without errors. -
现在我们已经看到任务成功执行,我们可以更改配置以指示我们希望它失败:
grunt.initConfig({ shouldFail: true }); -
如果我们现在再次使用
grunt check命令运行任务,它应该产生类似于以下内容的输出:Running "check" task Warning: Task "check" failed. Use --force to continue. Aborted due to warnings. -
如我们从输出中看到的那样,当将
shouldFail配置属性设置为true时,任务如预期那样失败了。
更多内容...
由于任务有多种类型和结构,因此我们有多种方式来指示它们的失败。现在我们将查看如何使非阻塞任务失败以及如何在失败时立即终止任务。
失败的非阻塞任务
如果我们使用了this.async函数来指示任务是否以非阻塞方式运行,那么任务函数的返回值就变得没有意义了。
如果我们希望使一个非阻塞任务失败,我们可以通过调用this.async方法返回的函数的第一个参数来实现。使用此参数提供false或一个Error对象将指示任务已失败。
在以下示例中,我们将任务从主配方中修改为以非阻塞方式运行,并适当失败:
grunt.registerTask(
'check',
'Fails if indicated in configuration.',
function () {
var done = this.async();
var fail = grunt.config('shouldFail');
if (fail === true) {
done(new Error('Error Message.'));
} else {
grunt.log.writeln('Success!');
}
}
);
在失败时立即终止任务
有时,我们可能希望遇到失败时立即终止整个 Grunt 进程。这种类型的行为通常与通常所说的致命错误相关联。
以下示例使用了grunt.fail.fatal实用工具来指示发生了致命错误,并且 Grunt 进程应该立即退出,而无需任何进一步的延迟:
grunt.registerTask(
'check',
'Fails if indicated in configuration.',
function () {
var fail = grunt.config('shouldFail');
if (fail === true) {
grunt.fail.fatal('Error message.');
} else {
grunt.log.writeln('Success!');
}
}
);
使用命令行参数
如果一个任务的配置方面需要经常更改,将其添加到项目配置中可能效率不高。这就是使用命令行参数可以派上用场的地方,因为它们为我们提供了一种轻松地为任务提供选项的方法,而无需更改我们的配置。
准备工作
在这个例子中,我们将使用在第一章中创建的基本项目结构,即在项目中安装 Grunt配方中的内容。如果您还不熟悉其内容,请务必参考。
如果这个配方中的任何步骤看起来难以理解,请务必查看本章前面提供的创建基本任务配方。
如何操作...
以下步骤将指导我们创建一个任务,该任务使用命令行参数接收两个字符串,并使用它们来打印一条消息:
-
我们将首先通过在 Grunt 文件中的主函数末尾添加以下代码来注册一个名为
welcome的任务,并添加一个描述和一个空的占位符函数:grunt.registerTask( 'welcome', 'Displays a welcome message.', function () { } ); -
在任务注册设置完成后,我们现在可以填充空函数,以接收命令行参数并使用它们来打印消息:
var message = 'Welcome to ' + city + ', ' + name + '!'; grunt.log.writeln(message); -
现在,我们可以使用一些示例参数来运行任务。这是通过使用
grunt welcome:Aaron:London命令来完成的,它应该产生类似于以下内容的输出:Running "welcome:Aaron:London" (welcome) task Welcome to London, Aaron!小贴士
注意,如果您的任务使用了目标,第一个参数将始终被认为是目标的名称,并且不会传递给任务函数。
-
从输出中我们可以看到,任务已成功接收我们提供的参数,并使用它们来显示欢迎消息。
将任务排队运行
有时候,我们可能希望在任务内部排队运行现有任务。这种做法并不推荐,因为设计良好的任务通常应该专注于特定的功能,并与其他任务保持松散耦合。以这种方式设计的任务通常在更广泛的场景中更健壮且更有用。
准备工作
在这个例子中,我们将使用我们在 第一章 中 在项目中安装 Grunt 菜谱中创建的基本项目结构,该菜谱在 使用 Grunt 入门 中。如果你还不熟悉其内容,请务必参考。
如果这个菜谱中的任何步骤看起来难以理解,请务必查看本章前面提供的 创建基本任务 菜谱。
如何操作...
以下步骤将引导我们创建两个简单的任务,其中一个任务打印一个字符串,而另一个任务在内部将其入队:
-
我们将首先注册一个名为
first的任务,该任务会打印出一个字符串。这可以通过将以下代码添加到我们的 Grunt 文件中的主函数末尾来完成:grunt.registerTask('first', function() { grunt.log.write('First Task'); }); -
接下来,我们将注册另一个任务,该任务将紧跟在我们刚刚添加的任务之后。这个任务也会打印一个字符串,然后继续将
first任务入队以运行:grunt.registerTask('second', function() { grunt.log.write('Second Task'); grunt.task.run('first'); }); -
现在我们已经创建了两个任务,我们可以通过运行
grunt second命令来尝试它们,这应该会产生类似于以下输出的结果:Running "second" task Second Task Running "first" task First Task -
如我们从输出中看到的那样,我们运行的第二个任务将第一个任务入队,该任务也运行了。
创建多任务
任务配置为我们使用在项目中的任务的功能调整提供了最常见的方式。通过配置,我们可以设置一个任务以满足我们的特定需求,并且它还允许我们在单个项目中以不同的方式使用同一个任务。应用于同一任务的不同的配置被称为 任务目标。
准备工作
在这个例子中,我们将使用我们在 第一章 中 在项目中安装 Grunt 菜谱中创建的基本项目结构,该菜谱在 使用 Grunt 入门 中。如果你还不熟悉其内容,请务必参考。
如果这个菜谱中的任何步骤看起来难以理解,请务必查看本章前面提供的 创建基本任务 菜谱。
如何操作...
以下步骤将引导我们创建一个简单的任务,该任务打印目标配置的名称和提供给它的数据:
-
我们将首先使用
registerMultiTask函数在 Grunt 文件的主函数末尾添加以下代码来注册一个名为display的多任务。grunt.registerMultiTask( 'display', 'Displays target name and related configuration', function() { } ); -
在任务注册后,我们可以用以下代码填充空函数,该代码打印包含指定目标名称和提供的数据的消息:
grunt.log.write( 'Displaying ' + this.target + ' with ' + this.data );小贴士
注意,只有在使用
registerMultiTask注册的任务函数的范围内,this.target和this.data才会被填充为目标名称和配置数据。 -
现在我们已经注册了一个功能正常的任务,我们可以为我们的
grunt.initConfig调用设置一些示例配置,我们将用它来演示任务的行为:grunt.initConfig({ display: { foo: 'these configurations', bar: 'those configurations' } }); -
在我们的任务创建和配置后,我们可以通过运行
grunt display命令来尝试它,该命令应该产生类似于以下输出的结果:Running "display:foo" (display) task Displaying foo with these configurations Running "display:bar" (display) task Displaying bar with those configurations -
现在,我们也可以尝试通过运行
grunt display:foo命令来指示我们只想使用foo配置目标,该命令应该产生类似于以下输出的结果:Running "display:foo" (display) task Displaying foo with these configurations
在任务中使用选项
调整任务功能的最常见方式是在项目配置中提供选项。选项可以通过在任务或目标级别提供一个对象到options属性来指定。如果同时在任务和目标级别提供了选项,则目标级别的选项将具有优先权。
准备工作
在本例中,我们将使用我们在第一章中创建的基本项目结构,即“在项目中安装 Grunt”配方中的结构,来工作。如果你还不熟悉其内容,请务必参考它。
如果这个配方中的任何步骤看起来难以理解,请务必查看本章前面提供的“创建多任务”配方。
如何操作...
以下步骤将引导我们创建一个多任务,该任务打印出在两个不同任务的两个可用级别提供的选项值:
-
我们将首先使用
registerMultiTask函数在 Grunt 文件中的主函数末尾添加以下代码来注册一个名为display的多任务:grunt.registerMultiTask( 'display', "Displays the value of the 'foo' option", function() { } ); -
在任务注册后,我们可以用以下代码填充空函数,该代码打印包含
foo选项值的消息:var options = this.options(); grunt.log.write("The value of foo is '" + options.foo + "'.");小贴士
注意,在任务函数的作用域中提供的
this.options方法将自动合并任务和目标选项,如果目标中提供了任务选项,则将覆盖任务选项。想要了解更多关于
this.options方法的信息,请访问以下网址: -
现在我们已经注册并启用了一个任务,我们可以设置一些样本配置,我们将使用它来演示任务的行为:
grunt.initConfig({ display: { options: { foo: 'initial' }, first: {}, second: { options: { foo: 'override' } } } }); -
在我们的任务创建并提供了样本配置后,我们可以通过运行
grunt display:first命令来尝试使用first目标,该命令应该提供类似于以下输出的结果:Running "display:first" (display) task The value of foo is 'initial'. -
现在我们已经看到了
first目标的行为,我们可以通过运行grunt display:second命令来尝试使用second目标,该命令应该产生类似于以下输出的结果:Running "display:second" (display) task The value of foo is 'override'.
在任务中使用文件
任务通常需要访问一组特定的文件,无论是使用它们包含的数据,还是以某种方式修改它。Grunt 通过src、dest和files属性提供了一种统一的方式来指定文件集,这些属性可以在任务或目标级别使用。
准备工作
在本例中,我们将使用我们在第一章 Chapter 1 中创建的 在项目中安装 Grunt 配方中的基本项目结构,即 开始使用 Grunt。如果您还不熟悉其内容,请务必参考。
如果这个配方中的任何步骤看起来难以理解,请务必查看本章前面提供的 创建多任务 配方。
如何操作...
以下步骤将引导我们创建一些样本数据文件和一个简单的打印其内容的任务:
-
首先,我们在名为
data的新目录中创建两个名为one.dat和two.dat的样本文件,并给它们以下内容:one.dat - Initial sample data. two.dat - Some more sample data. -
接下来,我们将使用
registerMultiTask函数注册一个名为display的多任务,通过将以下代码添加到我们的 Grunt 文件中的主函数末尾:grunt.registerMultiTask( 'display', "Displays the contents of the specified files.", function() { } ); -
注册任务后,我们现在可以填充空函数,以下代码将遍历指定的文件并打印其内容:
this.files.forEach(function(file) { file.src.forEach(function(filepath) { var content = grunt.file.read(filepath); grunt.log.write(content); }); });小贴士
注意,
this.files属性仅在多任务函数的作用域内提供。它提供了一个标准化列表,包含在任务和目标级别提供的所有文件配置。关于
this.files属性的更多信息,请访问以下网址: -
为了演示我们的任务,我们现在可以在
grunt.initConfig调用中添加一些配置,这将针对data目录中包含的所有文件:grunt.initConfig({ display: { sample: { src: 'data/*' } } }); -
一切准备就绪后,我们现在可以通过运行
grunt display命令来尝试任务,它应该产生类似于以下内容的输出:Running "display:sample" (display) task Initial sample data. Some more sample data.
更多内容...
除了从文件中读取之外,我们可能还会达到想要写入文件的程度。以下步骤将修改之前的配方,以便将指定文件的内容写入目标文件而不是打印到控制台:
-
首先,我们将修改之前注册的
display任务的函数中的代码,改为以下内容:this.files.forEach(function(file) { var content = file.src.map(function(filepath) { return grunt.file.read(filepath); }).join(''); grunt.file.write(file.dest, content); }); -
接下来,我们将修改项目配置,以指示输出应写入的目标文件:
grunt.initConfig({ display: { sample: { src: 'data/*', dest: 'output.dat' } } }); -
现在,我们可以通过再次运行
grunt display命令来尝试我们的修改后的任务,它应该产生类似于以下内容的输出:Running "display:sample" (display) task -
应该现在在我们的项目文件夹中有一个名为
output.dat的文件,其内容如下:Initial sample data. Some more sample data.
第九章. 插件编写
在本章中,我们将涵盖以下内容:
-
寻找插件
-
为插件做出贡献
-
设置基本的插件项目
-
创建插件的任务
-
为插件任务编写测试
-
为插件添加文档
-
发布插件
简介
在很大程度上,Grunt 是一个框架,它允许开发者以统一的方式打包和配置提供给网络应用开发者的各种工具。在 Grunt 领域,工具被打包成被称为插件的包,这些插件包都符合 Grunt 的操作和配置约定。
一些更受欢迎的插件是由 Grunt 核心团队提供的,但所有其他插件都是由使用 Grunt 的项目开发者社区贡献的。现在我们正在我们的项目中使用 Grunt,我们成为了这个社区的一部分,我们可以承担起协助创建和维护这些插件的职责。
寻找插件
在我们考虑创建自己的插件之前,我们首先应该确定是否已经存在一个能够满足我们需求的插件。在大多数情况下,你会发现其他人已经尝试解决你现在面临的问题,并且已经为你做了大部分甚至全部的工作。
准备工作
寻找插件最明显的地方是互联网。因此,我们首先需要做的是打开我们喜欢的网页浏览器并准备导航。
如何操作...
以下步骤将引导我们导航到 Grunt 项目的网站,并使用它来搜索我们可以用来运行并发任务的插件。
-
首先,我们将通过在我们的网页浏览器中输入以下 URL 来导航到官方 Grunt 插件列表页面:
gruntjs.com/plugins。 -
接下来,我们将选择搜索输入文本框,它应该看起来像以下这样:
![如何操作...]()
-
在文本框被选中后,我们现在可以开始输入我们想要搜索的术语;在这种情况下,我们将输入
concurrent作为我们的搜索词:![如何操作...]()
-
一旦我们完成输入,我们应该看到列表自动加载一段时间,然后显示搜索结果:
![如何操作...]()
-
现在我们看到的一个插件列表看起来可能就是我们要找的,我们可以点击列表项来查看其文档。插件的安装说明通常应该在文档页面的顶部附近。
![如何操作...]()
为插件做出贡献
一旦你找到一个与你的需求非常接近的插件,你可能会发现它的某些方面可能是有缺陷的、不完整的或缺失的。这就是你可以介入并为项目做出各种贡献的地方。
贡献插件项目可以为您提供从项目中获得您所需内容的优势,而无需自己创建整个项目。这对其他人,包括您自己,也有好处,因为他们遇到了您面临的问题或需要相同的附加功能。
入门
-
要进行本食谱中提到的任何贡献,需要一个 GitHub 账户。如果您还没有账户,创建一个账户就像访问 GitHub 主页并填写注册表单一样简单,请访问
github.com/。小贴士
以下网址提供了对 Git 和 GitHub 的全面介绍:
-
熟悉在 GitHub 问题部分创建 问题 的实践。创建和管理问题的机制相当简单,但在使用时应用良好的实践非常重要。良好的问题编写将产生重大差异,并有助于您在更短的时间内获得所需内容。您可以在以下位置了解更多关于良好问题编写实践的信息:
wiredcraft.com/posts/2014/01/08/how-we-write-our-github-issues.html -
为了检索任何插件项目的代码和文档,您需要安装 Git 版本控制软件。您可以在以下网址了解有关 Git 的各种安装选项:
-
如果您想将您对托管在 GitHub 上的项目所做的代码或文档更改贡献出来,您需要熟悉拉取请求(pull request)的工作流程。有关拉取请求工作流程的更多信息,请参阅以下网址:
-
为了从您对 Grunt 项目及其插件的贡献中获得最佳效果,请确保您熟悉 Grunt 贡献指南,该指南可在以下网址找到:
-
在您考虑贡献的项目中,务必仔细研究项目文档,以确保您正确使用它,并且您正在寻找的功能尚未提供。Grunt 插件项目的文档应始终位于项目仓库根目录下的
README.md文件中。小贴士
GitHub 默认会将位于仓库根目录的
README.md文档显示为仓库的主页。 -
每个项目可能都有自己的特定贡献指南,需要遵循。这些通常可以在
README.md的末尾或项目存储库根目录下的CONTRIBUTING.md文档中找到。
如何操作...
有许多方式可以为现有插件做出贡献。让我们根据其难度进行列表,从最简单的一个开始:
-
通过评论其内容、重现其错误或提供对所述问题的解决方案来帮助现有问题。
-
在遇到问题时添加问题。请注意,只有当您清楚地说明上下文和问题时,新问题才有帮助。如果您能想到一个潜在解决方案,那么提供该解决方案将更有帮助。
小贴士
一定要检查具有类似主题的现有问题。如果问题关注的是相同主题,通常最好是添加到它。然而,如果它与现有问题略有不同,那么在新的问题中引用它可能是个好主意。
在提交问题之前,也建议审查插件的文档。这是为了确保您遇到的问题或行为不是预期的,并且您实际上正在按照作者的意图使用插件。
-
添加可能改进的问题。始终,清楚地说明上下文和改进的概念很重要。
小贴士
一定要检查问题和 pull request,看看是否尚未请求或开发该功能。此外,审查文档以确保它尚未以其他形式可用。
-
提交对插件文档的更新和改进。这需要分支插件项目并提交包含更改的 pull request。
-
解决问题并将更改提交到代码库。这需要分支项目并提交一个 pull request。如果报告的问题尚未破坏测试,则添加一个针对特定用例的测试是至关重要的,该用例导致了错误。
-
实现新功能并将更改提交到代码库。这需要分支插件项目并提交一个 pull request。请注意,添加功能需要更新项目的文档并创建测试来确保新功能的行为符合文档描述。
设置基本插件项目
每个 Grunt 插件的基础都是一个包含其目的、版本、依赖等信息 Node.js 项目。由于所有 Grunt 插件的基项目结构几乎相同,我们将利用项目生成器为我们提供一个起点。
在这个菜谱中,我们将使用Yeoman项目的脚手架工具来生成我们的基本 Grunt 插件项目。它提供了一系列项目设置的生成器,所有这些项目都以 Grunt 作为其核心自动化工具。
小贴士
您可以在以下 URL 了解更多关于 Yeoman 项目的信息:
入门
此食谱的唯一要求是在全局安装 Node.js,并按照 第一章 中 安装 Grunt CLI 食谱的说明安装 Grunt。如果您还不熟悉其内容,请务必参考它。
如何操作...
以下步骤将指导您安装 Yeoman 工具并使用它生成一个基本的 Grunt 插件项目。
-
首先,我们需要使用 npm 工具全局安装 Yeoman 工具。这是通过输入以下命令完成的:
$ npm install --global yo -
我们还需要安装专门针对生成 Grunt 插件项目的 Yeoman 生成器。这是通过输入以下命令完成的:
$ npm install --global generator-gruntplugin -
接下来,我们将创建一个名为
grunt-myplugin的目录,它将包含我们的插件项目,并使用类似以下命令导航到它:$ mkdir grunt-myplugin && cd grunt-myplugin -
在安装了 Yeoman 和所需的生成器之后,我们现在可以通过运行以下命令来使用它生成我们的插件项目:
$ yo gruntplugin -
运行上一个命令后,您将看到一系列问题,这些问题将帮助生成器创建项目。这种交互应该类似于以下内容:
? Plugin Name: grunt-myplugin ? Description: The best Grunt plugin ever. ? Version: 0.0.1 ? Project git repository: git://github.com/me/grunt-myplugin ? Project homepage: http://github.com/me/grunt-myplugin ? License: MIT ? Author name: Me ? Author email: me@example.com ? Author url: http://me.example.com/ ? What versions of node does it run on? >= 0.8.0 ? What version of grunt does it need? ~0.4.2小贴士
在上一个示例中所有关于 '我' 的引用处,您应该输入您自己的姓名、账户名称和凭证。
-
在回答完所有问题后,生成器将创建一个简单插件所需的所有文件,具有一个功能任务。输出,类似于以下内容,将通知我们这些操作:
create tasks/myplugin.js create test/expected/custom_options create test/expected/default_options create test/fixtures/123 create test/fixtures/testing create test/myplugin_test.js create .jshintrc create .gitignore create .editorconfig create README.md create Gruntfile.js -
在创建了所有必要的文件之后,我们现在需要安装项目运行所需的依赖。这些依赖项列在生成的
package.json文件中,可以使用以下命令安装:$ npm install小贴士
此命令使用当前目录中找到的
package.json文件,因此请确保您已经导航到我们之前创建的grunt-myplugin目录,如果您还没有这样做的话。 -
确认项目已成功设置的最佳方式(除了在 Grunt 项目中实际使用它之外)是运行生成的测试。这可以通过以下命令完成:
$ npm test -
成功运行测试应该产生类似于以下内容的输出:
> grunt-myplugin@0.0.1 test /home/me/projects/grunt-myplugin > grunt test Running "clean:tests" (clean) task Running "myplugin:default_options" (myplugin) task File "tmp/default_options" created. Running "myplugin:custom_options" (myplugin) task File "tmp/custom_options" created. Running "nodeunit:tests" (nodeunit) task Testing myplugin_test.js..OK >> 2 assertions passed (18ms)
创建插件任务
Grunt 插件的功能主要包含在它们提供的任务中。Yeoman 工具提供的插件项目脚手架为我们提供了一个这样的任务,我们可以从中工作或作为创建我们自己的参考。
入门
在这个食谱中,我们将使用本章前面 设置基本插件项目 食谱中创建的基本项目结构。如果您还不熟悉其内容,请务必参考它。
此配方还包含在第八章的“创建多任务”、“在任务中使用选项”和“在任务中使用文件”配方中介绍的概念,这些配方可以在“创建自定义任务”部分的末尾找到。如果您想深入了解这些概念,请务必参考这些配方。
如何做到...
以下步骤将引导我们创建一个任务,该任务将连接所有指定的源文件,并在结果前添加包含时间戳和位置的注释。
-
我们将首先在
tasks目录中创建一个名为timestamp.js的新文件,该文件将包含我们的任务代码。将包含任务的文件命名为任务本身是一种良好的做法。 -
接下来,我们将设置包含我们的任务代码的代码模块,并通过填充以下代码来在模块内部注册新任务:
module.exports = function (grunt) { grunt.registerMultiTask( 'timestamp', 'Perpends a files contents with a timestamp.', function () { } ); };小贴士
Node.js 自动为每个文件提供
module.exports对象。将其分配给它的任何内容都将在文件被导入到另一个文件时可用。您可以在以下 URL 中了解更多关于module.exports的信息: -
在我们的任务内部,我们首先需要做的是检索任务目标的选项,并将其存储在
options变量中。我们还将为可用的选项提供一些默认值,以确保即使没有提供任何选项,我们的任务也能正常工作。以下代码负责此操作,并可以添加到任务函数的顶部:var options = this.options({ datetime: new Date(0), location: 'London' });小贴士
datetime选项的默认值是 Unix 时间范围内可用的最早值。此值是通过创建一个标准的 JavaScriptDate对象并为其提供0作为唯一参数来确定的。 -
接下来,我们将创建一个包含空字符串的
comment变量,然后逐步将我们的注释内容添加到其中。以下代码为我们执行此操作:var comment = ''; comment += '// ' + options.datetime.toGMTString(); comment += ' at ' + options.location + '\n'; -
在准备好我们的注释字符串后,我们现在可以连接所有源文件,并将注释和结果写入指定的目标文件。以下代码为任务配置中指示的所有文件执行此操作:
this.files.forEach(function(file) { var src = file.src.map(function (path) { return grunt.file.read(path); }).join(''); grunt.file.write(file.dest, comment + src); }); -
现在我们已经注册并使任务功能化,我们可以添加一些配置,以便我们可以通过它们来测试任务。让我们添加两个目标:一个名为
default_options的目标,它测试没有提供任何选项的任务,另一个名为custom_options的目标,它测试提供了所有可能选项的任务。我们可以在我们的 Gruntfile 中添加以下配置来实现这一点:timestamp: { default_options: { options: {} }, custom_options: { options: { datetime: new Date(Date.UTC(2014, 0, 1)), location: 'New York' } } }小贴士
我们在这里使用
Date.UTC函数来确保此代码在不同时区之间的行为不会有所不同。以这种方式设置日期将始终等于 GMT 时区中的日期和时间值。 -
为了使这些任务目标能够工作,它们还必须指明它们想要从中读取的源文件以及它们想要将结果写入的目标文件。在我们的测试中,我们将使用在
test/fixtures目录中由项目生成器创建的文件作为源文件,并将结果写入tmp/timestamp目录。这可以通过添加以下files配置来完成:default_options: { options: {}, files: { 'tmp/timestamp/default_options': [ 'test/fixtures/testing', 'test/fixtures/123' ] } }, custom_options: { options: { datetime: new Date(Date.UTC(2014, 0, 1)), location: 'New York' }, files: { 'tmp/timestamp/custom_options': [ 'test/fixtures/testing', 'test/fixtures/123' ] } } -
最后,我们可以使用
grunt timestamp命令来运行我们的任务,它应该产生类似于以下内容的输出:Running "timestamp:default_options" (timestamp) task Running "timestamp:custom_options" (timestamp) task -
为了确认任务运行正确,我们可以检查在
tmp/timestamp目录中创建的文件内容。例如,custom_options文件应该包含类似于以下内容:// Wed Jan 01 2014 00:00:00 GMT (SAST) at New York Testing1 2 3
为插件任务编写测试
测试的创建是所有编程模块开发的一个基本部分,包括 Grunt 插件。测试为我们提供了一种方式,以确认我们的任务在多种情况下都能按预期工作并保持工作。
开始
在这个菜谱中,我们将继续在本章前面“创建插件任务”菜谱中创建的项目进行工作。如果你还不熟悉其内容,请务必参考它。
如何做...
以下步骤将引导我们创建预期的输出文件和一个测试套件,以检查实际输出文件是否与它们匹配。
-
在我们开始创建实际的测试套件之前,我们将在
test/expected/timestamp目录中创建一个名为default_options的文件,该文件包含使用default_options目标运行任务时预期的输出:// Thu, 01 Jan 1970 00:00:00 GMT at London Testing1 2 3 -
接下来,我们将在同一目录中创建另一个名为
custom_options的文件,该文件包含使用custom_options目标运行文件时预期的输出:// Wed, 01 Jan 2014 00:00:00 GMT at New York Testing1 2 3 -
现在我们已经准备好了包含预期输出的文件,我们可以开始设置实际的测试套件,该套件将比较它们的与任务结果的差异。我们将在
test目录中创建一个名为timestamp_test.js的文件,并填充以下代码:var grunt = require('grunt'); module.exports.timestamp = { };小贴士
在文件开始处将 Grunt 库导入并分配给
grunt变量,以便在测试中使用。我们还在
module.exports对象中为任务分配了一个以任务命名的空对象。我们将把我们的测试函数添加到这个对象中。 -
在我们的测试套件的基本框架就绪后,我们现在可以为每个预期的结果添加一个测试。我们将按照之前创建的预期结果文件命名测试,将测试添加到
timestamp_test.js文件中导出的对象中:module.exports.timestamp = { default_options: function (test) { test.expect(1); var actual = grunt.file.read( 'tmp/timestamp/default_options' ); var expected = grunt.file.read( 'test/expected/timestamp/default_options' ); test.equal( actual, expected, 'should work without specifying options.' ); test.done(); }, custom_options: function (test) { test.expect(1); var actual = grunt.file.read( 'tmp/timestamp/custom_options' ); var expected = grunt.file.read( 'test/expected/timestamp/custom_options' ); test.equal( actual, expected, 'should work with custom options.' ); test.done(); } };小贴士
测试的格式通常由运行它们的框架决定。在我们的例子中,它基于生成的 Grunt 插件项目,我们使用nodeunit框架。你可以在以下 URL 了解更多关于该框架的信息:
-
现在我们已经设置了测试套件,我们需要指示
test别名任务在运行测试之前应该运行时间戳任务。这会导致任务生成可以与已设置为预期结果的文件进行测试的结果文件。这是通过在运行测试的nodeunit任务之前将timestamp任务添加到test别名任务中实现的:grunt.registerTask('test', [ 'clean', 'myplugin', 'timestamp', 'nodeunit' ]); -
最后,我们可以使用
npm test或grunt test命令来运行测试,这应该会产生类似于以下内容的输出:Running "clean:tests" (clean) task Cleaning tmp...OK Running "myplugin:default_options" (myplugin) task File "tmp/default_options" created. Running "myplugin:custom_options" (myplugin) task File "tmp/custom_options" created. Running "timestamp:default_options" (timestamp) task Running "timestamp:custom_options" (timestamp) task Running "nodeunit:tests" (nodeunit) task Testing myplugin_test.js..OK Testing timestamp_test.js..OK >> 4 assertions passed (73ms) -
我们现在可以通过接近结尾的输出看到,我们的测试已经成功运行,我们可以在开发过程中继续运行它们,以确保我们没有因为最新的更改而破坏任何东西。
为插件添加文档
高质量的文档对于大多数软件开发项目的成功至关重要。在 Grunt 插件的上下文中,其主要目的是提供有关插件及其任务的使用说明和信息。与 GitHub 上托管的大多数项目一样,插件的文档位于 README.md 文件中,并使用 Markdown 格式编写。
小贴士
关于 Markdown 通用格式的更多信息,您可以访问以下网址:
入门
在这个菜谱中,我们将继续在本章前面“创建插件任务”菜谱中创建的项目上工作。如果你还不熟悉其内容,请务必参考它。
如何做到这一点...
以下步骤将引导我们审查和添加由 Yeoman 和之前使用的 gruntplugin 生成器生成的文档。
-
让我们首先使用我们最喜欢的编辑器打开位于项目根目录中的
README.md文件。 -
首先,我们将在文档文件的开始部分检查标题和描述。根据我们项目的初始生成,标题
grunt-myplugin可能对我们来说仍然适用。然而,我们在这个插件中有一个新的任务,我们可能需要在描述中提到它。让我们将其更改为以下内容:> Our very first plugin with our very first "timestamp" task. -
接下来,我们可以回顾“入门”部分,以确保它仍然适合我们的项目。在这种情况下,生成的说明应该仍然足够。
-
在文档中进一步深入,我们现在应该看到插件中包含的任务列表。目前列出的第一个也是唯一一个任务是我们在项目中生成的
myplugin任务。让我们在myplugin任务部分之后添加以下标题,开始编写我们的timestamp任务文档:## The "timestamp" task -
我们将添加到我们新任务部分的第一个部分将是概述。这应该提供对任务目标和操作的基本解释。以下内容对于我们的新任务应该是足够的:
### Overview The `timestamp` task can be used to concatenate files and prepend the result with the a comment containing the provided time and location. -
现在我们可能需要提供有关可调整任务的选项的说明。让我们添加以下内容来解释
datetime和location选项:### Options #### options.datetime Type: 'Date' Default value: Minimum Unix time value A Date object that contains the date and time you'd like to print out in the prepended comment. #### options.location Type: 'String' Default: 'London' A string value that contains the location that you'd like to print out in the prepended comment. -
接下来,我们将为我们的任务添加一些使用示例。以下示例说明了使用默认选项和使用所有可用选项设置的任务:
### Usage Examples #### Default Options In this example we make use of all the default values of the options. This should concatenate the provided files and prepend the result with a comment containing the minimum Unix time, along with the location as 'Earth'. ```js grunt.initConfig({ timestamp: { options: {}, files: { 'dest/default_options': [ 'src/foo', 'src/bar' ] }, }, }) ```js #### Custom Options In this example we set all possible options. The `datetime` options is set to the first day in 2014 and the location is set to 'New York'. ```js grunt.initConfig({ timestamp: { options: { datetime: new Date(2014, 0, 1), location: 'New York' }, files: { 'dest/default_options': [ 'src/foo', 'src/bar' ] }, }, }) ```js This should produce a comment of the following form: ```js // Wed, 01 Jan 2014 00:00:00 GMT at New York ```js -
最后,我们可以回顾一下
Contributing、Release History和License部分的内容,以确保项目中没有我们想要更改或添加的内容。
发布插件
发布一个插件可能就是您进入开源开发和协作世界的入门。随着项目在社区中获得关注并变得对他人有用,您可能会开始看到对项目的贡献;这些贡献可能包括修复问题和实现您可能或可能没有在路线图中列出的功能。
入门
在这个菜谱中,我们将发布我们在本章中创建的项目,最终以为插件添加文档菜谱中的项目结束。如果您想了解我们是如何到达这个阶段的,请务必参考它及其前面的菜谱。
在我们能够将任何内容发布到node 包管理器注册表之前,我们需要在包托管服务中以用户身份进行注册。这可以在www.npmjs.com/signup完成。
如何操作...
以下步骤将指导我们如何将插件项目发布到 node 包管理器注册表:
-
首先,我们需要通过在命令行中输入
npm adduser命令并向 NPM 服务确认我们,然后输入我们在www.npmjs.com网站上注册的用户凭据。 -
接下来,我们应该确保我们处于我们想要发布的插件项目的根目录中。如果我们还没有在那里,我们应该在继续之前导航到那里。
-
一旦我们处于适当的目录中,发布我们的插件就像运行
npm publish命令一样简单,这应该会产生类似于以下内容的输出:+ grunt-myplugin@0.0.1 -
为了这个示例,我们还将使用
npm unpublish --force命令从注册表中删除我们的插件,这应该会产生类似于以下内容的输出:npm WARN using --force I sure hope you know what you are doing. - grunt-myplugin@0.0.1
第十章. 静态网站
在本章中,我们将涵盖以下食谱:
-
设置基本网站项目
-
在网站上添加页面
-
在网站上添加内容
-
向网站添加数据
-
创建和使用网站布局
-
从集合生成页面
-
创建模板助手
-
使用插件
简介
Grunt 真正出色的领域之一是管理静态网站项目。使用 Grunt 生成静态网站可以快速且易于修改,当其优势对情况不适用时,可以取代构建传统服务器生成网站的复杂性和成本。
在本章中,我们将专注于利用Assemble框架构建静态网站。在核心上,Assemble 是一个 Grunt 插件,但它也使用 Grunt 来整合所有其他相关工具。
例如,我们将构建一个简单的静态网站来代表我们在互联网上的个人形象。把它想象成通过万维网(WWW)公开的简历。
设置基本网站项目
为了加快基于Assemble框架启动新项目的过程,我们将利用开发者社区提供的Yeoman生成器。生成器将生成一个最小化项目设置,包括我们开始所需的一切。
开始使用
本食谱的唯一要求是在全局范围内安装 Node.js,并在其上安装 Grunt,如第一章中“安装 Grunt CLI”食谱所述,即“开始使用 Grunt”。如果您还不熟悉其内容,请务必参考。
如何操作...
以下步骤将指导您安装 Yeoman 工具并使用它生成一个基本的 Assemble 项目。
-
首先,我们需要使用
npm工具全局安装 Yeoman 工具。这是通过输入以下命令来完成的:$ npm install --global yo -
我们还需要安装专门针对生成 Assemble 项目的 Yeoman 生成器。这是通过输入以下命令来完成的:
$ npm install --global generator-assemble -
接下来,我们将创建一个名为
mysite的目录,它将包含我们的静态网站项目,并使用以下类似的命令进入该目录:$ mkdir mysite && cd mysite -
在安装了 Yeoman 和所需的生成器后,我们现在可以使用它通过运行以下命令来生成我们的插件项目:
$ yo assemble -
在运行上一个命令后,我们将被一系列问题所引导,这些问题将帮助生成器创建项目。这种交互应该看起来像以下这样:
? Your project name: mysite ? Your project description: The best project ever. ? Would you mind telling me your username on Github? me ? Do you want to install Ass emble plugins? No提示
确保将示例
meGitHub 用户名替换为您的用户名,并且选择不安装任何额外的 Assemble 插件。关于使用额外插件的内容将在本章后面讨论。 -
在回答完这些问题后,生成器将创建一个简单静态网站项目所需的文件。类似于以下输出的信息将告知我们这些操作:
create AUTHORS create CHANGELOG create LICENSE-MIT create Gruntfile.js create package.json create .editorconfig create README.md create .gitignore create .gitattributes create bower.json create src/data/site.yml create src/assets/theme.css create src/content/markdown.md create src/templates/pages/blog.hbs create src/templates/pages/index.hbs create src/templates/layouts/default.hbs create src/templates/partials/navbar-fixed-top.hbs -
在创建了上述文件之后,生成器也应该自动安装在新创建的
package.json和bower.json文件中提到的所需依赖项。然而,如果出现依赖项尚未安装的情况,你可以尝试使用以下命令手动安装:$ bower install & npm install -
在创建了必要的文件并安装了依赖项之后,我们现在可以使用
grunt server命令构建我们的网站并在本地托管。这应该会产生类似于以下内容的输出:Running "clean:0" (clean) task >> 0 paths cleaned. Running "copy:bootstrap" (copy) task Created 4 directories, copied 12 files Running "copy:theme" (copy) task Created 1 directories, copied 1 files Running "assemble:pages" (assemble) task Assembling dist/blog.html OK Assembling dist/index.html OK >> 2 pages assembled. Running "connect:livereload" (connect) task Started connect web server on http://localhost:9000 Running "watch" task Waiting... -
一旦
server任务完成,它应该会自动打开你的默认浏览器,并在http://localhost:9000/上显示标准生成的网站。在撰写本文时,它看起来如下所示:![如何操作...]()
向网站添加页面
作为其核心,一个静态网站仅仅是一系列页面的集合。在我们基本设置到位后,我们可以考虑添加一个包含我们个人一些传记信息的页面。
入门
在这个菜谱中,我们将使用我们在本章早期“设置基本网站项目”菜谱中创建的基本项目结构。如果你还不熟悉它的内容,请务必参考它。在我们开始之前,我们还应该确保网站构建器和服务器正在运行。如果它们还没有运行,可以使用grunt server命令启动它们。
如何操作...
以下步骤将引导我们向我们的静态网站添加一个包含我们简单传记的页面:
-
首先,我们将在
src/templates/pages目录中创建一个名为bio.hbs的文件,并填充一些关于我们新页面的信息:--- title: Bio heading: 'Biography' ---小贴士
文件的前部分被称为YAML 前文部分,它可以用来向网站构建工具提供有关页面的元数据。你可以在以下网址了解更多信息:
-
在将我们的页面信息添加到文件后,我们可以继续添加页面的 HTML 内容。让我们通过在文档开头部分之后添加以下内容来完成这个操作:
<div class="jumbotron"> <h1>{{ heading }}</h1> <p>Building awesome sites since 2014.</p> </div>Assemble 项目生成器提供的默认模板语言是 Handlebars,这在前面代码中已经使用。你可以在以下网址了解更多关于 Handlebars 的信息:
确保注意,前文部分中提供的元数据可以在页面模板中使用。一个这样的例子可以在前面代码中使用
heading变量时看到。 -
在保存我们新创建的页面文件后,最初启动的网站构建器应该自动检测到它的添加,并继续重新编译我们的网站,生成类似于以下内容的输出:
>> File "src/templates/pages/bio.hbs" added. Running "assemble:pages" (assemble) task Assembling dist/bio.html OK Assembling dist/blog.html OK Assembling dist/index.html OK >> 3 pages assembled. -
一旦编译完成,它应该刷新我们的浏览器对
http://localhost:9000/的视图,之后我们应该在网站顶部的导航栏中看到一个新的Bio项目。如果我们选择此项目,我们应该看到一个新页面,其外观可能如下所示:![如何操作...]()
向网站添加内容
在开发一个网站时,通常建议将网站的结构和内容分开。以这种方式组织可以使我们轻松编辑网站不断变化的内容,而无需每次都涉及相对静态的结构。
Assemble框架附带了一个实现Markdown文本格式化语法的库;这使我们能够轻松创建具有最小结构的内容文档,并将其渲染为格式良好的 HTML。
您可以在以下 URL 了解更多关于 Markdown 语法的知识:
开始使用
在这个菜谱中,我们将继续本章中“向网站添加页面”菜谱中的项目。如果您还不熟悉其内容,请务必参考。在我们开始之前,我们还应该确保网站构建器和服务器正在运行。如果它们还没有运行,可以使用grunt server命令启动它们。
如何操作...
以下步骤将指导我们创建一个 Markdown 文档来包含我们的传记,并在页面中使用它。
-
首先,我们将在
src/content目录中创建一个名为bio.md的新文件,并用我们的传记内容以 Markdown 格式填充它:## Achievements * Built this awesome site * Mastered Grunt * Explored Assemble ## History Born in a galaxy far, far away.小贴士
在以下 URL 中可以找到 Assemble 支持的 Markdown 语法的便捷速查表:
-
在创建内容文件后,我们现在需要在我们的网站上使用它。我们将通过更改
src/templates/pages/bio.hbs文件的内容来实现这一点:--- title: Bio heading: 'Biography' --- <div class="jumbotron"> <h1>{{ heading }}</h1> {{md 'src/content/bio.md'}} </div>小贴士
mdHandlebars助手由附带在 Assemble 中的handlebars-helpers包提供。它可以通过提供字符串或包含文件路径的变量来渲染 Markdown 文件。 -
保存修改后的
bio.hbs文件后,我们最初启动的网站构建器应该会自动检测到它已被修改,并继续重新编译我们的网站,生成类似于以下内容的输出:>> File "src/templates/pages/bio.hbs" changed. Running "assemble:pages" (assemble) task Assembling dist/bio.html OK Assembling dist/blog.html OK Assembling dist/index.html OK >> 3 pages assembled. -
如果我们现在查看位于
http://localhost:9000/bio.html的传记页面,我们会看到我们的新内容现在显示在上面;它应该看起来像以下这样:![如何操作...]()
向网站添加数据
很常见的情况是,网站上的大部分内容都遵循某种模式,并且只有内容的一部分在不同页面之间有所不同。例如,显示文章的网站将使用相同的布局显示每篇文章,并以重复的方式列出它们。作为开发者,我们可能希望避免手动创建这些结构相似的页面或项目。
为了这个目的,通常建议将我们希望以这种方式显示的内容存储为数据,然后我们可以使用模板或布局来渲染它们,每个模板都将使用相同的模板呈现。
入门
在这个菜谱中,我们将继续使用本章前面找到的“向网站添加内容”菜谱中的项目。如果您还不熟悉其内容,请务必参考它。在我们开始之前,我们还应该确保网站构建器和服务器正在运行。如果它们尚未运行,可以使用grunt server命令启动它们。
如何做到这一点...
以下步骤将指导我们添加表示导航栏项的数据,并使用这些数据来定制导航栏的渲染。
-
由于我们的网站只有一个导航栏,我们不妨将其直接添加到位于
src/data目录中的现有site.yml文件中。此文件用于存储与网站本身相关的数据。让我们向其内容中添加以下sections集合:title: Mysite sections: - title: Home dest: 'dist/index.html' - title: Bio dest: 'dist/bio.html' - title: Blog dest: 'dist/blog.html'小贴士
前一个文件的数据以 YAML 格式存储。以下 URL 提供了有关 YAML 格式的更多信息:
yaml.org/。 -
接下来,我们将修改导航栏的模板,以使用我们在
site.yml文件中提供的sections数据。为此,我们首先需要在位于src/templates/partials目录中的navbar-fixed-top.hbs文件中找到以下代码块:{{#each pages}} <li{{#if this.isCurrentPage}} class="active"{{/if}}> <a href="{{relative dest this.dest}}">{{data.title}}</a> </li> {{/each}} -
一旦我们找到了上述代码块,我们可以将其替换为根据
sections数据列出导航项的代码。这是通过替换为以下代码来完成的:{{#each site.sections}} <li {{#is dest ../page.dest}} class="active"{{/is}}> <a href="{{relative ../page.dest dest}}">{{title}}</a> </li> {{/each}}小贴士
在这里,
#is辅助函数用于检查页面的完整路径名是否与导航栏项应链接到的路径名相同。如果它们匹配,则该项应呈现为当前激活项。在这里,
relative辅助函数用于确定从当前页面到导航项应链接到的页面的相对路径。这里提到的两个辅助函数都是由与 Assemble 捆绑的
handlebars-helpers库提供的。Assemble 框架根据目录包含的文件名与变量同名,使
src/data目录中的数据可用。这就是我们如何在之前的示例中使用site变量名来访问site.yml文件中的数据。 -
保存修改后的
navbar-fixed-top.hbs文件后,我们最初启动的网站构建器应该会自动检测到它已被修改,并继续重新编译我们的网站,生成类似于以下内容的输出:>> File "src/templates/partials/navbar-fixed-top.hbs" changed. Running "assemble:pages" (assemble) task Assembling dist/bio.html OK Assembling dist/blog.html OK Assembling dist/index.html OK >> 3 pages assembled. -
如果我们现在查看
http://localhost:9000/上的任何页面,我们会看到导航栏中的项目现在按照sections数据中项目的顺序排列:![如何操作...]()
创建和使用网站布局
在整个网站上保持一致的布局可以极大地提高其可用性,同时也能在开发过程中节省时间。Assemble 框架允许我们轻松地以各种方式创建和使用布局。
准备工作
在这个菜谱中,我们将继续在本章的 向网站添加数据 菜谱中工作的项目。如果您还不熟悉它的内容,请务必参考它。在我们开始之前,我们还应该确保网站构建器和服务器正在运行。如果它们还没有运行,可以使用 grunt server 命令启动它们。
如何操作...
以下步骤将引导我们创建一个新的布局模板,并设置一些使用它的示例博客页面。
-
首先,我们将创建一个新的布局模板,它将为我们的示例博客文章页面提供布局。让我们在
src/templates/layouts目录中创建一个名为post.hbs的文件,并填充以下内容:--- layout: src/templates/layouts/default.hbs --- <div class="row"> <div class="col-xs-3"> <ul class="nav nav-pills nav-stacked"> {{#each pages}} <li{{#if this.isCurrentPage}} class="active"{{/if}}> <a href="{{relative dest this.dest}}"> {{data.title}} </a> </li> {{/each}} </ul> </div> <div class="col-xs-9"> <h1>{{title}}</h1> {{> body}} </div> </div>小贴士
在文件的顶部 YAML 前置部分,我们使用了特殊的
layout属性来指示应该用于此模板的父模板。您可以在以下网址了解更多关于 Assemble 中模板继承如何工作的信息:构建此布局所使用的 HTML 和 CSS 组合是由默认包含在 Assemble Yeoman 生成器中的 Bootstrap 项目提供的。您可以在以下网址了解更多关于 Bootstrap 的信息:
-
现在,我们将创建一些使用我们新创建的布局的示例博客页面。我们将通过在
src/templates/pages/posts目录中创建三个文件来完成此操作,文件名为one.hbs、two.hbs和three.hbs。每个文件都应该包含类似于以下内容:--- title: One --- <p>This is post one.</p>小贴士
这些页面的内容可以是几乎任何东西,只要至少有一个包含
title和之后一些 HTML 内容的前置部分。 -
为了使我们的新创建的页面能够渲染,我们必须添加一个新的
assemble任务目标。然而,这个目标将共享现有pages目标的大部分选项,所以接下来我们将把pages目标的options移动到assemble任务配置本身中,以便它可以在assemble任务的各个目标之间共享:assemble: { options: { flatten: true, assets: '<%= config.dist %>/assets', layout: '<%= config.src %>/templates/layouts/default.hbs', data: '<%= config.src %>/data/*.{json,yml}', partials: '<%= config.src %>/templates/partials/*.hbs' }, pages: { files: { '<%= config.dist %>/': [ '<%= config.src %>/templates/pages/*.hbs' ] } } } -
现在所有目标都共享了选项,我们可以添加一个
task目标,该目标将为我们渲染示例文章页面。让我们向组装任务添加以下名为posts的任务目标:posts: { options: { layout: '<%= config.src %>/templates/layouts/post.hbs', }, files: { '<%= config.dist %>/posts/': [ '<%= config.src %>/templates/pages/posts/*.hbs' ] } } -
为了让我们能够到达这些新生成的页面并对博客文章列表页面进行一些更改。这可以通过更改
src/templates/pages目录中blog.hbs文件的內容来实现:--- title: Blog --- <h1>Posts</h1> <ul> <li><a href="/posts/one.html">One</a></li> <li><a href="/posts/two.html">Two</a></li> <li><a href="/posts/three.html">Three</a></li> </ul> -
在我们继续之前,我们还需要处理我们在将我们的示例页面模板放置在
src/templates/pages/posts目录时引起的一个轻微副作用。默认的watch任务设置,该设置触发自动编译更改的模板,没有配置为在src/templates/pages目录本身之外查找更改。这意味着对我们示例页面模板的更改不会触发重新编译。让我们通过更改watch任务的assemble目标的files配置来解决这个问题,如下所示:assemble: { files: [ '<%= config.src %>' + '/{content,data,templates}/**/*.{md,hbs,yml}' ], tasks: ['assemble'] } -
最后,我们可以重新启动我们运行的服务器,以便配置更改生效。重启后,我们应该看到一些关于渲染我们的示例文章页面的额外输出:
Running "assemble:posts" (assemble) task Assembling dist/posts/one.html OK Assembling dist/posts/three.html OK Assembling dist/posts/two.html OK >> 3 pages assembled. -
如果我们现在导航到我们的网站中的博客部分
http://localhost:9000/blog.html并跟随列表中的一个链接,我们现在应该看到使用我们新的布局模板渲染的所选文章页面:![如何操作...]()
从集合生成页面
如果我们在网站数据中有一个项目集合,我们很可能想使用该集合生成一些页面。例如,显示博客文章的网站可能希望为集合中的每篇文章生成一个单独的页面。
入门
在这个菜谱中,我们将继续本章中“创建和使用站点布局”菜谱中的项目工作。如果你还不熟悉它的内容,请务必参考它。在我们开始之前,我们还应该确保站点构建器和服务器正在运行。如果它们还没有运行,可以使用grunt server命令启动它们。
如何操作...
以下步骤将引导我们创建一个包含我们博客文章集合的数据文件,并配置我们的项目使用该集合生成页面。
-
让我们从创建我们将从中生成页面的集合数据开始。我们可以通过在
src/data目录中创建一个名为posts.yml的文件并给它以下内容来实现这一点:- filename: one data: title: One content: <p>This is post one.</p> - filename: two data: title: Two content: <p>This is post two.</p> - filename: three data: title: Three content: <p>This is post three.</p>小贴士
在每个页面对象中,
filename属性指示要生成的页面的文件名,data属性可以用来向页面发送任何类型的元数据,而content属性可以用来提供应由body标签渲染的内容。上述格式由
assemble任务的pages选项确定,该选项将在本菜谱的后面使用。 -
现在我们已经将博客帖子放入数据集合中,我们可以删除之前用来表示它们的页面模板。这可以通过简单地删除整个
src/templates/pages/posts目录来完成。 -
在我们的集合准备就绪并且旧的页面模板被移除后,我们现在可以更改我们的配置以从集合中渲染帖子页面。我们将通过在
assemble任务中对posts目标进行以下更改来实现这一点:Posts: { options: { layout: '<%= config.src %>/templates/layouts/post.hbs', pages: grunt.file.readYAML('src/data/posts.yml') }, files: {'<%= config.dist %>/posts/': []} }小贴士
应该用于生成新页面的集合是通过
pages选项指示的。正如前一个示例所示,我们还在使用grunt.file.readYAML实用程序来加载我们之前创建的src/data/posts.yml文件中的集合。文件对象中的目标不再指示应该渲染的特定
files,而是现在指示新生成的页面应该保存到的目录。 -
最后,我们可以重新启动在后台运行的服务器,以便配置更改生效。重启后,我们应该会看到一些额外的输出,这些输出与从提供的集合中渲染我们的帖子页面有关:
Running "assemble:posts" (assemble) task Assembling dist/posts/one.html OK Assembling dist/posts/two.html OK Assembling dist/posts/three.html OK >> 3 pages assembled. -
如果我们现在在我们的网站上导航到博客帖子项,我们应该会看到它们现在反映了由帖子集合提供的资料。
创建模板助手
在构建页面模板的过程中,我们经常会遇到在渲染逻辑中重复出现的模式。模板助手允许我们将这些已识别的模式之一封装起来,以便在网站上的模板中轻松重复使用。
可以向模板助手传递参数,并且助手本身的逻辑是使用常规编程语言实现的,这使得使用它们所能实现的可能性相当多。
开始使用
在这个菜谱中,我们将继续从“从集合生成页面”菜谱中的项目工作。如果您还不熟悉它的内容,请务必参考它。在我们开始之前,我们还应该确保网站构建器和服务器正在运行。如果它们还没有运行,可以使用grunt server命令启动它们。
如何操作...
以下步骤将引导我们创建一个模板标签,用于自动列出我们博客页面上的帖子,使用的是帖子集合中的数据。
-
让我们从创建
lib/helpers目录开始,该目录用于包含包含我们助手代码的源文件。 -
现在我们已经准备好了目录,我们可以在
lib/helpers目录中创建一个名为pages.js的文件,并填充以下代码:module.exports.register = function( Handlebars, options, params ) { Handlebars.registerHelper( 'pages', function(target, options) { var config = params.grunt.config; var pages = config.get('assemble')[target].options.pages; var result = ''; for(var i = 0; i < pages.length; i++) { result = result + options.fn(pages[i]); } return result; } ); };小贴士
上述助手使用由
target参数提供的任务目标名称来查找适当的目标配置。然后,它从中提取pages集合并遍历此集合来渲染助手的主体,每个项目作为上下文。你可以在以下 URL 上阅读更多关于此文件结构和注册自定义助手的信息:
-
为了遵循我们的 Grunt 配置文件约定,我们将在
config任务中添加一个属性,指示将包含可重用库的目录名称。添加属性后,任务应类似于以下内容:config: { lib: 'lib', src: 'src', dist: 'dist' } -
由于我们在项目中添加了更多以模板助手形式存在的代码,我们可能需要修改
watch任务,以便模板助手代码的更改也会触发我们网站的重建。这可以通过对watch任务中的assemble目标进行以下更改来实现:assemble: { files: [ '<%= config.src %>' + '/{content,data,templates}/**/*.{md,hbs,yml}', '<%= config.lib %>/helpers/*.js' ], tasks: ['assemble'] } -
接下来,我们将修改我们的配置,以加载
lib/helpers目录中的助手,使它们在我们的所有模板中可用。这可以通过向assemble任务的选项集合中添加以下helpers选项来实现:options: { flatten: true, assets: '<%= config.dist %>/assets', layout: '<%= config.src %>/templates/layouts/default.hbs', data: '<%= config.src %>/data/*.{json,yml}', partials: '<%= config.src %>/templates/partials/*.hbs', helpers: '<%= config.lib %>/helpers/*.js' } -
在我们的模板助手加载并准备好使用后,我们可以在网站的博客页面上使用它来列出我们帖子集合中所有帖子的链接。我们通过将
src/templates/pages/blog.hbs的内容更改为以下内容来实现这一点:--- title: Blog --- <h1>Posts</h1> <ul> {{#pages 'posts'}} <li> <a href="/posts/{{filename}}.html"> {{data.title}} </a> </li> {{/pages}} </ul> -
最后,我们可以重新启动在后台运行的
server,以便配置更改能够生效。重启后,我们应该看到在http://localhost:9000/blog.html网站博客页面渲染的列表现在反映了帖子集合中的项目:![如何操作...]()
使用插件
为了扩展其核心功能,Assemble 框架提供了一个插件系统。目前只有少数几个插件可用于 Assemble,但拥有插件系统使得我们能够轻松创建和重用我们自己的扩展,甚至可能与社区分享。
准备工作
在这个菜谱中,我们将继续从 创建模板助手 菜谱中工作的项目。如果你还不熟悉它的内容,请务必参考它。在我们开始之前,我们还应该确保网站构建器和服务器正在运行。如果它们还没有运行,可以使用 grunt server 命令启动它们。
如何操作...
以下步骤将引导我们使用 assemble-middleware-sitemap 插件生成一个格式良好的 sitemap,该文件可以被网络爬虫用于导航和索引网站。
-
首先,我们需要安装包含
assemble-middleware-sitemap插件的包,并将其保存到我们的包依赖中。以下命令为我们完成了这项工作:$ npm install assemble-middleware-sitemap -
接下来,我们需要指示插件应该被
assemble任务的全部目标使用。这可以通过在 Grunt 配置文件中添加plugins配置选项到assemble任务中来实现,指定新安装的插件包名称:options: { flatten: true, assets: '<%= config.dist %>/assets', layout: '<%= config.src %>/templates/layouts/default.hbs', data: '<%= config.src %>/data/*.{json,yml}', partials: '<%= config.src %>/templates/partials/*.hbs', helpers: '<%= config.lib %>/helpers/*.js', plugins: ['assemble-middleware-sitemap'] } -
最后,我们可以重新启动在后台运行的我们的服务器,以便配置更改能够生效。重启后,我们应该看到
robots.txt和site.xml文件已经在dist目录中生成了。






















浙公网安备 33010602011771号