本文作者:IMWeb helinjiang 原文出处:IMWeb社区 未经同意,禁止转载
最近写了两个Grunt插件(grunt-htmlstamp 和 grunt-file-modify),已经用在自己的项目中,用得很开心。本文便是记录了Grunt插件开发的一些关键的点,作为笔记,比较简明扼要,更适合对Grunt有一些了解的同学,一些基础的知识请自行 Google 之。
本文中的演示在 windows 操作系统中,Linux 或 OS X 可能有一些小区别。
一、环境准备
1. 安装 Node.js
在 Node.js 官网 选择合适的版本下载并安装。安装完成之后,打开 cmd 命令窗口,输入 node -v
,如输出版本号,则证明安装成功。
2. 安装 grunt-cli
根据 Grunt 官方的文档 ,打开 cmd 命令窗口,输入 npm install -g grunt-cli
进行安装。安装完成之后,再输入 grunt -version
,如输出版本号,则证明安装成功。
注意:npm 默认的源下载包地址在国外,由于某些缘故,下载包的速度可能会比较慢,建议使用国内的镜像,例如 淘宝npm镜像 等。如果在内网使用,还可能涉及到配置代理才能正常访问。
3. 安装合适的 IDE
我推荐使用 webstorm ,因为它的调试功能很好用。当然其他的开发工具也是OK的,用得顺手就行。
4. 安装 Git
除非你不打算将你的插件使用 Git 来做版本管理(当然你也可以选择 SVN等),否则需要 windows 用户请前往 https://git-for-windows.github.io/ 下载安装;同时如果你需要可视化工具,可以安装完 Git 之后,再安装 TortoiseGit。
5. 在 GitHub 中注册帐号
除非你不打算将你的插件使用 GitHub 来管理源码(当然你也有很多其他的选择),否则需要前往 https://github.com/ 进行注册。
6. 在 npm 中注册帐号
除非你不打算将你的插件上传到 npm 上,否则需要前往 https://www.npmjs.com/ 进行注册。
二、新建项目
1. Grunt 插件的命名
开发一个 Grunt 插件,首先得有个合(xiang)适(liang)的名字。按照约定,Grunt 插件是以 grunt-
开头命名,但要注意:
"grunt-contrib"
命名空间保留给 Grunt 团队维护的task使用,我们要避免使用;- 确定好名字之后,请在 npm 官网 中搜索这个名字是否已经被占用了,如已被占用,请更换名字。
在本文的示例中,我们姑且将我们的这个插件叫做 grunt-mytest
。
2. 新建工程
确定了插件名称,则新建工程。例如我们的工程路径为: D:codegrunt-mytest
。
3. 新建 GitHub 仓库
虽然不是必须的,但很有必要建立一个仓库,用于源码管理和 issue 等跟踪。一般而言,如果你的插件是开源的话,推荐使用 GitHub ,国内的话还有 Git@OSC 等可选择。例如我们申请的github仓库地址为:https://github.com/helinjiang/grunt-mytest ,其过程为:
三、使用项目脚手架
目前主要有两种方式能够快速生成项目脚手架,一种是官方推荐的使用 grunt-init
工具,另外一种是借助 Yeoman。
方式一: grunt-init 和 grunt-init-gruntplugin
官方推荐的方式。参考 官方指引 ,整理如下:
1. 安装 grunt-init
通过 npm install -g grunt-init
命令安装 grunt-init 。
2. 安装 gruntplugin
通过 git clone git://github.com/gruntjs/grunt-init-gruntplugin.git ~/.grunt-init/gruntplugin
命令安装grunt插件模版。但注意如果在 windows 下,则需要保存的地址需要修改为 %USERPROFILE%.grunt-initgruntplugin
,其中的 %USERPROFILE%
一般为 C:Users[user]
。
如果当前系统中未安装 git ,则直接到 GitHub 中下载 grunt-init-gruntplugin ,并将其中的内容放置在 C:Users[user].grunt-initgruntplugin
目录中。
注意:window下无法直接将一个文件夹命名为
.grunt-init
,可以借助 cmd 命令,cd
到C:Users[user]
,然后输入命令:md .grunt-init
。
3. 执行 grunt-init gruntplugin
在 cmd 中,cd
到 D:codegrunt-mytest
,执行 grunt-init gruntplugin
,将出现交互式问答,一步一步进行即可,例如:
方式二: Yeoman 和 generator-gruntplugin
参考 generator-gruntplugin 文档指引,整理如下:
1. 安装 Yeoman
进入 Yeoman 官网,按照相应提示安装 Yeoman。
执行 npm install -g yo
即安装了Yeoman,并且执行 yo --version
来确定是否安装成功。
2. 安装 generator-gruntplugin
执行 npm install -g generator-gruntplugin
即可。
3. 执行 yo gruntplugin
在 cmd 中,cd
到 D:codegrunt-mytest
,执行 yo gruntplugin
,将出现交互式问答,一步一步进行即可。
四、开发前
在正式编写代码之前,有必要再补充点技能。
1. 脚手架的目录结构
脚手架存在的意义是为了提供范本,以便我们能够按照这个样子书写我们的插件。因此在动手写代码之前,我们先看看其目录结构(只展示重要的文件):
代码语言:javascript复制|-task
|-mytest.js
|-test
|-expected
|-custom_options
|-default_options
|-fixtures
|-123
|-testing
|-mytest_test.js
|-Gruntfile.js
|-package.json
其中我们的task任务就在 /tast/mytest.js 中。而单元测试的用例则在 /test/mytest_test.js 中。自动生成的项目的构建任务很简单,就是合并多个文件,并提供了两个选项。当我们运行 grunt
命令之后,构建就开始了,并且还执行了单元测试。
2. 文档
我们需要一些必要的文档资料,包括 API 等:
- Creating plugins
- Grunt API
- Node.js API
由于 Grunt 本身就是基于 Node.js 开发的,因此理论上使用 Node.js 的 API 就能做很多事,但我们还是推荐尽量使用 Grunt 提供的 API ,它可以提高开发效率。
五、开发中
在我们的示例中,我们主要打交道的是 /tasks/mytest.js 文件,因为我们的 task 就是定义在这里。由于每个插件的目的不一样,因此无法讨论太多编程细节,这里只讨论几个点。
1. 不要闭门造车
每个 Grunt 插件存在,都有其特定的目的,但也无外乎是“对某些文件(src),依据某些配置(options),进行某些处理”,并且每个 task 任务还可能有多个 target 目标。最难的就是要熟悉它这一套是怎么玩的,熟悉了其规则(比如如何获得所有的合法src文件、如何保存文件、如何处理多target的场景等等),剩下的事情就很好办了。
如果我们的经验不够,最好的方式就是参考目前大量使用的插件是怎么玩的,尤其是官方提供的插件。比如按照对 src 文件的处理方式的不同,grunt-contrib-clean
和 grunt-contrib-copy
就属于两种典型代表。
grunt-contrib-clean
直接操作 src ,其典型用法如下。比较适合直接对原文件进行处理的场景,比如我的 grunt-file-modify 。
clean: {
build: {
src: ["path/to/dir/one", "path/to/dir/two"]
}
release: ["path/to/another/dir/one", "path/to/another/dir/two"]
}
grunt-contrib-copy
需要配置 src 和 dest,其典型用法如下。大部分的 Grunt 插件应该都是这种 。
copy: {
build: {
src: 'src/*',
dest: 'dest/',
},
release: {
files: [
{
expand: true,
cwd: 'path/',
src: ['**'],
dest: 'dest/'
},
],
},
}
我之所以举这个例子,只是为了说明除了看 API 之外,也需要结合看看人家的代码,这样能够更深刻理解。
2. 断点,单步调试
在前文中我提到过推荐使用 webstorm ,主要是因为它断点调试非常容易。有一段时间里,我喜欢使用 console
平台打印日志来调试,后来发现这种方式效率极低;我知道应该也有一些同学和我一样,还没有完全习惯去单步调试,这也是为什么我要在此特意强调的。
请尝试用单步调试,因为在调试的过程中你可以发现很多的细节,这些细节单靠查资料和看文档是无法获得的。比如我在开发过程中,在 grunt.registerMultiTask
内很需要获得当前 task 执行的 target 名字,即执行 grunt copy:build
时,我能获得 target="build"
,而执行 grunt copy:release
时,我能获得 target="release"
。通过断点,我找到了三个可能的取值:
grunt.task.current.name
: 目前调用的任务名字,两种情况值都为“copy”
,不符合我的预期grunt.cli.tasks[0]
: 最外层调用的task名字,分别为target="copy:build"
和target="copy:release"
,不符合我的预期this.target
: 分别为target="build"
和target="release"
,符合我的预期
3. 按功能迭代
完成一个功能块,且测试无问题之后,及时合入代码,迭代开发。千万别一口气完成很多改动再合入,这样不仅无法跟踪代码合入情况,而且一旦出错之后回滚也成了大问题。
同时如果功能已经基本完成之后(当然你也可以一开始就这么做),可以使用 issue 来跟踪 bug 和新的需求。如果你使用的是 GitHub 来管理源码(可能其他的也有此功能),你新建了 issue 之后,如果合入代码时在合入记录中输入这个isuue的网址,那么,这个 issue 中自动会将本次合入记录进行管理,这也意味着你的每个 issue 都可以找到对应的代码合入记录了。你可以到 https://github.com/helinjiang/grunt-htmlstamp/issues/2 看效果:
六、单元测试
不要偷懒,一定要写对应的单元测试。可能有些人会愁麻烦,但就我的亲身经历而言,它带来的好处远远大于所谓的“麻烦”。尤其是你的功能增多时,你每次的修改都有可能影响到之前OK的代码,单靠自己手动检视代码是没法保证代码质量的。
在本例中,写单元测试用例只需要在 /test/mytest_test.js 中写就行了。
虽然测试用例完全覆盖所有场景是很难达到,也很费时间去写用例,但覆盖主要场景却是必须的。在代码合并或发布之前,一定要确保已经写的用例都通过了单元测试。
七、readme.md
脚手架已经帮我们生成了一份,我们只需要在此基础上进行完善即可。它是我们插件的说明,因此尽可能描述清楚用法,以便其他人能够快速上手使用你的插件。
八、发布到 npm
如果你已经在 https://www.npmjs.com/ 中注册了帐号,则首先要在你本地的环境中增加该用户信息。在 cmd 中输入 npm login
,按提示在客户端中进行登录。如果你没卸载 npm ,则一般只需要登录一次即可。
如果已经在客户端中登录过,cd
到 D:codegrunt-mytest
,执行 npm publish
即可。当然由于我们演示的是个示例,因此是不会真实发布到 npm 上面的。
发布成功之后,可以在 https://www.npmjs.com/ 进行搜索你的插件,或者直接输入 https://www.npmjs.com/package/grunt-mytest 就能进入到你插件的主页面了。
九、一些容易遇到的坑
- 注意如果readme.md中有中文字符,一定要检查这个文件的编码格式为
utf-8
,否则即便它在 GitHub 中显示正常,但在 npm 网站上面,中文会显示为乱码。 npm publish
到 npm 上时,npmjs.com的数据可能不会实时刷新,其reademe.md的信息也一样,有可能需要等待几分钟。同时,各种 npm 镜像同步也需要一点时间(一般也允许你手动触发同步)。- 在脚手架生成的交互式问答中,description 中千万别输入单引号或者双引号,否则生成的代码会出现问题(你可以自己亲自试一试)。即便使用转义符转义,还是可能会有一些意外的场景。
- 如果我们修改了readme.md,每次
pull
了代码到 GitHub 上之后, GitHub 上显示的是最新的文档;但 我们执行npm publish
到 npm 上时,却发现 npm 上面的文档并未更新。除非你同时修改了package.json中的版本号。
The README displayed on the site will not be updated unless a new version of your package is published, so you would need to run npm version patch and npm publish to have a documentation fix displayed on the site. —— Updating the package