You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
What we need is a way of expressing these "dependencies" between plugins and their host package. Some way of saying, "I only work when plugged in to version 1.2.x of my host package, so if you install me, be sure that it's alongside a compatible host." We call this relationship a peer dependency
介绍
Grunt 是一款基于 Nodejs 的任务工具,并不如大多数文章介绍的只局限于前端自动化工具,只是大多数情况下应用于前端的重复性任务。大多数场景下,Nodejs 可以干什么,它也就可以干什么。
这篇文章从 介绍,新手入门,核心源码分析,进阶 - Grunt 的卡顿缘何引起? 以及 其他 这五个节组成,最后一节是填坑。
新手入门
准备
选择一个目录,新建项目目录,并进入
创建 src 目录,并在其中新建 grunt-tutorial.js
第一步,安装全局
grunt-cli
模块第二步 安装项目所要用到的
grunt
模块第三步 编写 Gruntfile.js
假设项目根目录下已经有了_package.json_, 如果没有请先运行
npm init
补上该文件。Gruntfile.js 是个 js 文件,所以,按写 js 的形式,想怎么写,就怎么写。但是如果新手想知道如何上手,和上面一样,我建议,按照官方给出的例子,复制一份。
这个环节涉及到的知识点
-g
, 一个却是--save-dev
。挖坑,下面会提到。附送一个官方教程: Getting started
接下来看实际应用。
试用 Grunt 提供的插件
假设你现在的 Gruntfile.js 是从官方 copy 的,长下面这个样子,这是前提。
那么现在安装所要执行任务的依赖插件 grunt-contrib-uglify
执行完毕后,就是使用了
命令行应该会输出
DONE, without errors
字样,OK。这个环节用到的知识点
DIY
在 grunt-tutorial 目录下新建目录 custom-tasks, 创建文件 frog.js,内容如下
打开 Gruntfile.js,在
grunt.loadNpmTasks('grunt-contrib-uglify');
下面添加一行现在可以在命令行中执行
这个时候,应该也是会输出一下信息的。
这个环节的知识点
本节提到的三个 API是使用 Grunt 的过程中非常重要。
到这里我想,大部分人应该说是可以达到会用 grunt 的程度了,但是还差一步,不做这一步,依然无法畅快使用 Grunt。那就是熟悉常用的 API,除了上面提到的几个,包括但不限于:
最好在使用的过程中把 API 列表都看一遍,混个眼熟,在用的时候能想起来就可以。
核心源码分析
grunt 的安装使用分为两个组成,一个是全局的 grunt-cli,为了区分暂时叫做global-grunt。一个是项目目录下的 grunt 模块,叫做 local-grunt 吧。
全局 grunt-cli
不算命令行补全的话,只做了一件事:通过 resolve 和 findup 来查找当前目录的 grunt 模块,并启动。
关键几行代码,从这里可以看出,查找到项目 grunt 模块后,直接调用
cli
API来启动local-grunt。local-grunt
剩余所有的事情都是由 local-grunt 中处理完成的。
最关键的是解析命令行参数,并传递给相应任务并启动任务,输出执行结果。
这里代码比较多,摘重要的一点点来看。下面是关键部分的目录结构
因为 global-grunt 调用了local-grunt 中 /lib/grunt.js 的 cli 接口,就从这里开始入口。
cli 是在 /lib/grunt/cli.js 定义的,负责解析命令行相关的逻辑,比如接收并格式化处理命令行传递进来的参数。
接下来,我们转回/lib/grunt.js ,看看 grunt.tasks 都干了什么。
有一个部分,很关键,但上面一笔带过了,就是加载task。这一切以 Gruntfile.js 为切入点来看一看。
上面提到了 Gruntfile.js 是在
task.init(tasks);
这里加载并执行的。那么现在看看这个函数。不要停,继续跟 loadTask 。
所以说,不管在命令行实际调用时执行哪个 task,在 Gruntfile.js 中,只要写了类似 loadNpmTasks 或者 loadTasks这样的代码,这个阶段它都会将这个任务加载进来。m(_ _)m。
还记得自定义任务 grunt.loadTasks 吗,也是通过 loadTask 来加载的(/lib/grunt/task.js),代码略过。
到这里了,接下来让任务 真正 执行起来吧,入口在Task.prototype.start 这里。
寻找脉路,看runTaskFn。
到此,核心代码和一个完整的流程就介绍完了。
这个环节结束,现在,我想你应该知道为什么有两个 grunt 模块了吧。简单来说就是 grunt-cli 为了查找 local-grunt 模块同时启动它。
看到这里,大家对 grunt 的核心代码应该是有一个轮廓性的大致了解。使用的话,估计是没问题了。
附一张图来帮助理解 Grunt 的整体结构。
如果你还想为 grunt 提点速的话,请继续往下看。否则可以关了这篇文章干别的去。
进阶 - Grunt 的卡顿?
关于 Gruntfile.js 的执行
上面一部分其实埋了一个伏笔,提到了关于 grunt 的加载插件的时机。试想如下场景
当我们的 Gruntfile.js 是这样写:
而我们在使用的时候,并不一定每次都会调用全部加载进来的插件,大部分情景可能是只使用其中一个,比如说这样:
其实在内部逻辑上,不管你使用的是哪个插件, Grunt 都会为你默认加载所有写在 Gruntfile.js 中的组件。(备注,这么说不太严格,但是目前绝大多数Gruntfile.js 写法都是会被加载,譬如说下面这种情况就不会被加载,但是这种写法的使用太少。)
这个问题,也是我在使用的过程中发现,当任务数稍微多些,此时在命令行执行
grunt taskname
时都会有一定的卡顿才会由执行任务过程和结果的输出。插件 time-grunt
为了确定这个问题是由 Grunt 引起,所以就找来了这么一个工具 time-grunt,它能显示出 Grunt 在执行任务的时候各个阶段的耗时,大概是这样样子:
明显能对比, load 阶段的时间大于执行任务的时间,当任务数越多,load 就会越耗时间(取决于 task 的数目和复杂度,task 一多这个耗时非常明显),问题确定了就开始改造 Grunt 吧。
插件 grunt-task-loader
问题提炼一下,在初始化执行 Gruntfile.js 时已经加载了全部的任务,而大多数情况都用不上。
怎么解决呢,所以第一步,不要让它在初始化的时候加载插件,换句话说就是全部去掉出现在 Gruntfile.js 中 grunt.loadNpmTasks这样的语句。
第二步 在任务执行阶段的前期来加载该任务相关 js 代码。
上面在核心源码分析这一部分,提到了 /lib/grunt.js 文件中这样一行代码以及相关它的注释。
该函数的作用:将各个任务的详细信息放入 task 实例的任务队列
_queue
。所以就选择这里作为切入点,通过劫持
Task.prototype.run
函数 来实现加速 Grunt 的需求。代码如下:
现在,再来通过 time-grunt 来看一下时间消耗情况。
明显降少了 load task 阶段的时间消耗,其实就是只 load 本次任务需要用到的插件。
对了,最好不要修改 Grunt 的源代码,和 time-grunt 类似,尽量以插件的形式修改,这同时就要求这样的插件的执行时机要尽量“早一些”。
这个环节介绍的其实就是 grunt-task-loader 插件的由来。
其他
最后,填一个坑,**为什么一个是
-g
, 一个是--save-dev
?**这里面其实涉及到两个点,一个是 npm 包的安装方式,一个是 npm 包的依赖管理方式,这俩问题又存在一些交叉,因为都是在安装 npm 包。当安装一个npm 包时,与 -g 对应的选项是不带 -g 的形式,前者是全局安装,后者会根据情况决定是安装在当前项目目录下或者用户目录下。
另一个问题,npm 包的依赖,常见的存在三种形式:
require('grunt')
的代码来,即使这些插件的 node_modules 目录下存在着一份 grunt 的拷贝,也不会被实际应用到。摘一段官方的描述:via: Peer Dependencies ,这篇文章从该依赖诞生的原因,要解决的问题说的很清楚了。
其实还有两个,optionalDependencies 和 bundledDependencies。 前者是可选,即使依赖包出错,npm 也可以继续初始化。后者是一组包名,他们会在发布的时候被打包进去,不太常用,我对这俩者的了解也只局限于此了。
EOF
The text was updated successfully, but these errors were encountered: