1.概述
kibana是一个完全基于JavaScript实现的B/S应用。下文以源码工程结构和构建管理作为对其研究的切入点,进行详细分析。
技术关键词: node; nvm; npm; bower; grunt; Gruntfile; load-grunt-config; jade; less; jshint; jscs; mocha
2.源码外层目录结构
最外层结构如下图所示:
其中相对不重要的目录是:
docs
:文档目录test
:测试代码目录
比较重要的目录是:
-
src
:源码目录 -
tasks
:构建任务目录 另外还有几个关键的文件: -
package.json
:包定义文件 -
Gruntfile.js
:Grunt任务定义文件 -
bower.json
:bower依赖管理定义文件
以下,对上述较重要文件与目录分述其中内容和作用,进而了解Kibana4.0源码工程结构与组织方式,以及其中涉及的多种技术和工具。
3.包定义文件:package.json
该文件是kibana的包定义文件。论及此文件应从CommonJS的模块化与包规范说起。
JavaScript先天缺少“模块”功能,其它高级语言中Java有类文件可以被import,Python也有import机制,Ruby有require机制,PHP有include和require机制,而JS一直缺乏类似的对等模块化机制。
CommonJS规范为JS弥补了此点,为JS提出了一套Modules规范。而Node则为该规范提供了一套非常易用的实现。具体内容不在此展开,只是以类比方式简单介绍CommonJS规范和Node实现为我们带来了什么:模块 和 包。
对比熟悉的Java平台来说,
- 模块可以对等为Java类,就像一个按照既定方式定义的.java文件定义了一个Java类一样,一个按照既定方式定义的.js文件可以定义一个JS模块。
- 不同的是:
- .java文件会被编译为.class文件,然后在JVM的类路径下被加载;而.js模块不需要被编译,node运行时同样也有一套类似于Java classpath的加载机制来加载所需的模块
- Java类有Java类的定义语法,而JS模块也同样有既定的定义方式
- 不同的是:
- 包可以对等为Java中的Jar,就像Jar包将众多相关的Java类组织成聚集一样,Node中的包也同样将众多的相关模块组织在一起以方便开发和应用。
- 不同的是:
- Jar包是一个压缩打包文件,而Node包则是一个直接表现为一个目录(至少包被使用时是非打包状态)
- 标准的Jar包结构基本是约定好的比较稳定,而Node包的结构虽然也隐含了CommonJS的规范约定,但包定义文件的内容中可以发挥更多的内容
- 不同的是:
Kibana源码中的package.json
文件就是kibana包定义文件。以Java的思维来看的话,如果说kibana最终会被发布为一个Jar的话(事实上kibana最终确实发布为一个CommonJS规范下的‘包’),package.json
就是其中的manifest文件,只是相对来说较之于普通的manifest文件更为重要。这是一个JSON格式的文件,其内容如下:
{
"name": "kibana",
"description": "Kibana is an open source (Apache Licensed), browser based analytics and search dashboard for Elasticsearch. Kibana is a snap to setup and start using. Kibana strives to be easy to get started with, while also being flexible and powerful, just like Elasticsearch.",
"keywords": [
"kibana",
"elasticsearch",
"logstash",
"analytics",
"visualizations",
"dashboards",
"dashboarding"
],
"private": false,
"version": "4.1.3-snapshot",
"main": "src/server/app.js",
"homepage": "https://www.elastic.co/products/kibana",
"bugs": {
"url": "http://github.com/elastic/kibana/issues"
},
"license": "Apache-2.0",
"author": "Rashid Khan <rashid.khan@elastic.co>",
"contributors": [
"Spencer Alger <spencer.alger@elastic.co>",
"Chris Cowan <chris.cowan@elastic.co>",
"Joe Fleming <joe.fleming@elastic.co>",
"Lukas Olson <lukas.olson@elastic.co>",
"Juan Thomassie <juan.thomassie@elastic.co>",
"Shelby Sturgis <shelby@elastic.co>",
"Khalah Jones-Golden <khalah.jones@elastic.co>"
],
"scripts": {
"test": "grunt test",
"start": "node ./src/server/bin/kibana.js",
"postinstall": "bower install && grunt licenses --check-validity",
"precommit": "grunt lintStagedFiles"
},
"repository": {
"type": "git",
"url": "https://github.com/elastic/kibana.git"
},
"dependencies": {
"ansicolors": "^0.3.2",
"bluebird": "^2.0.7",
"body-parser": "^1.10.1",
"bunyan": "^1.2.3",
"commander": "^2.6.0",
"compression": "^1.3.0",
"cookie-parser": "^1.3.3",
"debug": "^2.1.1",
"elasticsearch": "^3.1.1",
"express": "^4.10.6",
"glob": "^4.3.2",
"http-auth": "^2.2.5",
"jade": "^1.8.2",
"js-yaml": "^3.2.5",
"lodash": "^2.4.1",
"request": "^2.40.0",
"requirefrom": "^0.2.0",
"semver": "^4.2.0",
"serve-favicon": "^2.2.0",
"through": "^2.3.6"
},
"devDependencies": {
"bower": "^1.4.1",
"bower-license": "^0.2.6",
"event-stream": "^3.1.5",
"expect.js": "^0.3.1",
"grunt": "^0.4.5",
"grunt-cli": "0.1.13",
"grunt-contrib-clean": "^0.5.0",
"grunt-contrib-compress": "^0.9.1",
"grunt-contrib-copy": "^0.5.0",
"grunt-contrib-jade": "^0.10.0",
"grunt-contrib-jshint": "^0.11",
"grunt-contrib-less": "^0.10.0",
"grunt-contrib-requirejs": "^0.4.4",
"grunt-contrib-watch": "^0.5.3",
"grunt-esvm": "^1.0.1",
"grunt-jscs": "^1.8.0",
"grunt-mocha": "^0.4.10",
"grunt-replace": "^0.7.9",
"grunt-run": "^0.2.3",
"grunt-s3": "^0.2.0-alpha.3",
"grunt-simple-mocha": "^0.4.0",
"html-entities": "^1.1.1",
"http-proxy": "^1.8.1",
"husky": "^0.6.0",
"istanbul": "^0.2.4",
"license-checker": "^3.1.0",
"load-grunt-config": "^0.7.0",
"marked": "0.3.3",
"marked-text-renderer": "^0.1.0",
"mkdirp": "^0.5.0",
"mocha": "^2.2.5",
"npm": "^2.11.0",
"opn": "^1.0.0",
"path-browserify": "0.0.0",
"progress": "^1.1.8",
"requirejs": "^2.1.14",
"rjs-build-analysis": "0.0.3",
"simple-git": "^0.11.0",
"sinon": "^1.12.2",
"sinon-as-promised": "^2.0.3",
"tar": "^1.0.1"
},
"engines": {
"node": "~0.10 || ~0.12",
"iojs": ">=1.5"
}
}
对于开发来讲比较重要的几个关键信息如下:
- dependencies:运行时依赖
- devDependencies:开发期依赖
- engines:运行环境要求
除此之外,还有main
指明了程序入口,repository
指明了代码托管位置,scripts
指明了几个可运行的有用的自动化脚本。
从最关键的信息说起,就是那两个依赖数组了:dependencies
和devDependencies
。开发期依赖和运行期依赖的区别十分容易理解,那么具体被依赖的内容是什么呢?对此回答:是“包”。
比如,devDependencies中第一个依赖的是bower
包,并且标注了版本号信息。可以联想maven的依赖管理,但并不尽相同,这里的依赖信息意义上更接近于OSGi bundle的依赖。
包定义文件里对依赖进行了说明,那么如何获取这些依赖呢?借助NPM可以帮助用户快速安装和管理依赖包。NPM之于Node,相当于gem之于Ruby,pear之于PHP。所以,目前安装Node,默认是将NPM也附带安装的。
4.Node的安装与版本管理——NVM
NVM是Node Version Manager的缩写。先前主要是由于Node版本的迭代速度很快,版本很多的原因,所以才存在了该物件。概括地说,就是用脚本来管理本机上多个安装版本,灵活切换活跃版本,省得来回改变环境变量;并且提供便利的管理功能,一览当前所有的版本,另外可以从设定的远程仓库直接下载安装Node指定版本。不过自从io.js合并进来之后,目前貌似已经不再纠结版本问题了,稳定了许多。但是,nvm这种工具还是挺好用的,尤其是对于需要在多版本运行时调试开发的选手来说。
安装Node可以直接在官网上下载对应系统的部署版本,比如windows平台上直接运行msi安装文件即可。当然也可以使用NVM这样的高级工具。若第一次使用,建议按前者路数。
安装完毕后,比如使用msi安装包自动化安装后,Node命令即存在与系统path路径中。以下命令即可检查安装是否成功:
node -v
v4.1.0
5.Node打包模块——NPM
如上文所述,包规范的定义可以帮助Node解决依赖包安装的问题,而NPM正式基于该规范进行了实现。最初NPM工具是由Isaac Z.Schlueter单独创建,提供给Node服务的Node包管理器,需要单独安装。后来,在v0.6.3版本时集成进Node中作为默认包管理器,作为软件包的一部分一起安装。之后,Isaac Z.Zchlueter也成为了Node的掌门人。NPM是"Node Packaged Modules"的简称。
Node安装后,NPM命令也被加入到了当前的系统path中,运行如下命令即可试验:
npm -v
2.14.3
命令:
npm install <package-name>
将会在当前目录下创建node_modules目录,然后在node_modules目录下创建'package-name'目录,接着将对应的包下载下来并放于该位置。
为什么会创建node_modules目录呢?那是因为Node的模块(包)加载机制影响的,类似于java中的classpath机制,Node在执行代码时,遇到模块(包)加载的指令,寻址的过程就包括在当前目录或者当前的祖先目录中的node_modules目录中寻找被请求的模块(包)。更详细的内容参见CommonJS规范与Node的模块加载实现。
如果给上述命令加一个参数-g
,命令:
npm install <package-name> -g
-g
是将一个包安装位全局可用的可执行命令。它将根据包描述文件中的bin字段的配置,将实际脚本链接到与Node可执行文件相同的路径下。事实上,这种通过全局模式安装的所有模块包都被安装进了一个统一的目录下。
另外需要说明的一点是,NPM连接一个远程仓库(或是叫源)来自动下载依赖包,鉴于大局域网的情况,缺省的设在国外的源很难连上。好在国内有官方源镜像,配置变更方法:
npm config set registry "http://registry.npm.taobao.org/"
Node环境准备好了,下面我们在kibana源码最外层目录下执行命令:
npm install
上述命令,将读取当前目录(kibana源码最外层目录)下的package.json
文件,并自动将其中dependencies
与devDependencies
两个数组定义的包依赖,下载到当前目录的node_modules
子目录中。
6.前端依赖管理工具——bower
Bower 是 twitter 推出的一款依赖管理工具,基于nodejs的模块化思想。就技术上来说,它是基于Node和NPM之上的。就功能意义上与NPM进行比较的话,虽然同样都是面向包管理方面,但这玩意主要是为前端UI的开发所服务的。举个例子来说:
一个新的web项目开始,我们总是很自然地去下载需要用到的js类库文件,比如jQuery,去官网下载名为jquery-1.10.2.min.js文件,放到我们的项目里。当项目又需要bootstrap的时候,我们会重复刚才的工作,去bootstrap官网下载对应的类库。如果bootstrap所依赖的jQuery并不是1.10.2,而是2.0.3时,我们会再重新下载一个对应版本的jQuery替换原来的。
如此看来,在浏览器端的JS开发过程中同样也面临着复杂的依赖管理问题。尤其是开发重客户端应用时。
Kibana正是典型的SPA(单页应用),客户端相对来说非常重量级,所以自然而然地面临着依赖管理问题,同样地自然而然就使用了bower工具。
刚才说过,bower本身就是一个node模块,应该使用NPM来进行安装,只不过需要注意应该使用全局安装的方式:
npm install bower -g
如此,bower就可以在任何目录下执行了。
bower的用法与NPM的用法相当类似。可以看到,kibana源码最外层目录下存在一个bower.json
文件,该文件为bower说明了具体的依赖。其内容如下:
{
"name": "kibana",
"version": "0.0.0",
"authors": [
"Spencer Alger <spencer@spenceralger.com>"
],
"description": "Browser based analytics and search interface to Logstash and other timestamped data sets stored in ElasticSearch",
"main": "src/index.html",
"keywords": [
"kibana",
"elasticsearch"
],
"license": "Apache 2.0",
"homepage": "http://www.elastic.co/products/kibana",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular": "1.2.28",
"angular-bindonce": "0.3.3",
"angular-bootstrap": "0.10.0",
"angular-elastic": "2.4.2",
"angular-mocks": "1.2.28",
"angular-route": "1.2.28",
"angular-ui-ace": "0.2.3",
"bluebird": "~2.1.3",
"bootstrap": "3.3.4",
"d3": "3.4.13",
"elasticsearch": "4.1.0",
"Faker": "1.1.0",
"FileSaver": "babc6d9d8f",
"font-awesome": "4.2.0",
"gridster": "0.5.6",
"jquery": "2.1.4",
"leaflet": "0.7.3",
"Leaflet.heat": "Leaflet/Leaflet.heat#627ede7c11bbe43",
"lesshat": "3.0.2",
"lodash": "2.4.2",
"moment": "2.9.0",
"moment-timezone": "0.0.6",
"ng-clip": "0.2.6",
"require-css": "0.1.8",
"requirejs": "2.1.18",
"requirejs-text": "2.0.14",
"lodash-deep": "spenceralger/lodash-deep#1a7eca8344",
"marked": "0.3.3",
"numeral": "1.5.3",
"leaflet-draw": "0.2.4"
},
"devDependencies": {},
"resolutions": {
"angular": "1.2.28"
}
}
在kibana目录下,直接执行命令:
bower install
就可以自动下载bower.json
中定义的所有依赖js了。kibana的bower管理依赖将会被下载到:源码根目录/src/kibana/bower_components
目录下。bower默认会下载依赖到bower.js
同级目录的bower_components
子目录下,但是kibana源码目录中的.bowerrc
文件的设定覆盖了默认设置:
{
"directory": "./src/kibana/bower_components"
}
前端的依赖,自然要与前端开发代码放在同一个目录下了。
7.项目构建工具——Grunt
Grunt目前基本上是Javascript世界的标准构建工具。功能上等同于Java世界的Maven。
与bower相同,同样需要通过NPM安装Grunt(记得全局安装)。
npm install grunt -g
Grunt在使用界面上同Maven很类似,其实背后的设计思想都很一致,Grunt同样是基于插件和任务的设计思路。比如执行Maven命令:
mvn clean install
指定了maven的执行任务:clean 和 install。
Grunt也是如此,比如:
grunt test build
指定了grunt的执行任务:test 和 build。
其实,到此为止,如果你在kibana的目录中执行:
grunt dev
就可以在开发模式下启动kibana服务了。
8.Kibana的Gruntfile与任务
Maven
有POM
文件,对应地,Grunt
有Gruntfile
文件。
Kibana源码目录中的Gruntfile文件内容如下:
module.exports = function (grunt) {
// set the config once before calling load-grunt-config
// and once durring so that we have access to it via
// grunt.config.get() within the config files
var config = {
pkg: grunt.file.readJSON('package.json'),
root: __dirname,
src: __dirname + '/src', // unbuild version of build
build: __dirname + '/build', // copy of source, but optimized
app: __dirname + '/src/kibana', // source directory for the app
plugins: __dirname + '/src/kibana/plugins', // source directory for the app
server: __dirname + '/src/server', // source directory for the server
target: __dirname + '/target', // location of the compressed build targets
buildApp: __dirname + '/build/kibana', // build directory for the app
configFile: __dirname + '/src/server/config/kibana.yml',
nodeVersion: '0.10.35',
platforms: ['darwin-x64', 'linux-x64', 'linux-x86', 'windows'],
services: [ [ 'launchd', '10.9'], [ 'upstart', '1.5'], [ 'systemd', 'default'], [ 'sysv', 'lsb-3.1' ] ],
unitTestDir: __dirname + '/test/unit',
testUtilsDir: __dirname + '/test/utils',
bowerComponentsDir: __dirname + '/src/kibana/bower_components',
devPlugins: 'vis_debug_spy',
meta: {
banner: '/*! <%= package.name %> - v<%= package.version %> - ' +
'<%= grunt.template.today("yyyy-mm-dd") %>\n' +
'<%= package.homepage ? " * " + package.homepage + "\\n" : "" %>' +
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= package.author.company %>;' +
' Licensed <%= package.license %> */\n'
},
lintThese: [
'Gruntfile.js',
'<%= root %>/tasks/**/*.js',
'<%= src %>/kibana/*.js',
'<%= src %>/server/**/*.js',
'<%= src %>/kibana/{components,directives,factories,filters,plugins,registry,services,utils}/**/*.js',
'<%= unitTestDir %>/**/*.js',
'!<%= unitTestDir %>/specs/vislib/fixture/**/*'
],
lessFiles: [
'<%= src %>/kibana/components/*/*.less',
'<%= src %>/kibana/styles/main.less',
'<%= src %>/kibana/components/vislib/styles/main.less',
'<%= plugins %>/dashboard/styles/main.less',
'<%= plugins %>/discover/styles/main.less',
'<%= plugins %>/settings/styles/main.less',
'<%= plugins %>/visualize/styles/main.less',
'<%= plugins %>/visualize/styles/visualization.less',
'<%= plugins %>/visualize/styles/main.less',
'<%= plugins %>/table_vis/table_vis.less',
'<%= plugins %>/metric_vis/metric_vis.less',
'<%= plugins %>/markdown_vis/markdown_vis.less'
]
};
grunt.config.merge(config);
var dirname = require('path').dirname;
var indexFiles = grunt.file.expand({ cwd: 'src/kibana/plugins' }, [
'*/index.js',
'!' + config.devPlugins + '/index.js'
]);
var moduleIds = indexFiles.map(function (fileName) {
return 'plugins/' + dirname(fileName) + '/index';
});
config.bundled_plugin_module_ids = grunt.bundled_plugin_module_ids = moduleIds;
// load plugins
require('load-grunt-config')(grunt, {
configPath: __dirname + '/tasks/config',
init: true,
config: config
});
// load task definitions
grunt.task.loadTasks('tasks');
};
如果你看过Grunt的快速入门的话,那么你将发现以上文件内容跟文档中的说明存在较大出入。原因是kibana使用了Grunt的稍高级点的玩法。
- 首先,kibana使用了Grunt插件——
load-grunt-config
插件,将原本集中在Gruntfile的所有任务配置按任务拆分开来,放在单独的小文件中。这在Gruntfile比较庞大的情形下,更有利于配置维护工作。 - 其次,kibana并没有使用常见的
grunt.task.loadNpmTasks
API,而是使用了grunt.task.loadTasks
。[grunt.task.loadTasks(tasksPath)](http://www.gruntjs.net/api/grunt.task#grunt.task.loadtasks)
是从指定的目录(注意:相对于 Gruntfile 所在目录)加载任务相关的文件。文件名就是任务名称。
所以,kibana的Gruntfile里只是定义了一些全局的配置信息。具体的任务和任务配置都在task
目录里。
加载任务定义文件:
// load task definitions
grunt.task.loadTasks('tasks');
加载任务配置文件:
// load plugins
require('load-grunt-config')(grunt, {
configPath: __dirname + '/tasks/config',
init: true,
config: config
});
什么叫“任务定义”,什么叫“任务配置”?暂且可理解为:前者重于过程,侧重于模板化的过程定义;后者重于数据,侧重于模板参数的设置。
8.1 执行:grunt dev —— 开发模式下启动kibana
kibana的dev任务定义如下:
module.exports = function (grunt) {
var _ = require('lodash');
grunt.registerTask('dev', function () {
var tasks = [
'less:dev',
'jade',
'esvm:dev',
'maybe_start_kibana',
'watch'
];
if (!grunt.option('with-es')) {
_.pull(tasks, 'esvm:dev');
}
grunt.task.run(tasks);
});
};
很清晰地,dev任务包含了以下几个子任务:
- 'less:dev'
- 'jade'
- 'esvm:dev'
- 'maybe_start_kibana'
- 'watch'
这些任务将顺序执行。
首先是[less](http://lesscss.org/)
编译css文件。less源文件在Gruntfile中被定义如下:
root: __dirname, // kibana源码最外层目录
src: __dirname + '/src', // unbuild version of build
plugins: __dirname + '/src/kibana/plugins', // source directory for the app
//...
lessFiles: [
'<%= src %>/kibana/components/*/*.less',
'<%= src %>/kibana/styles/main.less',
'<%= src %>/kibana/components/vislib/styles/main.less',
'<%= plugins %>/dashboard/styles/main.less',
'<%= plugins %>/discover/styles/main.less',
'<%= plugins %>/settings/styles/main.less',
'<%= plugins %>/visualize/styles/main.less',
'<%= plugins %>/visualize/styles/visualization.less',
'<%= plugins %>/visualize/styles/main.less',
'<%= plugins %>/table_vis/table_vis.less',
'<%= plugins %>/metric_vis/metric_vis.less',
'<%= plugins %>/markdown_vis/markdown_vis.less'
]
less编译完毕后,接下来是jade任务。jade是一种html5模板技术,用 JavaScript 实现的,并且可以供 Node 使用。此处不展开。
第三个任务是esvm:dev
。这个任务是关于要不要启动elasticsearch的。如果已经准备好了es服务,那么grunt dev
命令本身也就无需with-es
参数了,那么该任务将会被忽略。
第四个任务是maybe_start_kibana
,该任务是用来检测是否已经有kibana在运行,并且只有在指定端口上未有实例运行的前提下才会启动kibana server。maybe_start_kibana
任务定义如下:
module.exports = function (grunt) {
var config = require('./utils/server-config');
var maybeStartServer = function (options) {
return function () {
var http = require('http');
var opts = {
method: 'HEAD',
path: '/',
host: 'localhost',
port: options.port
};
grunt.log.debug('checking for server', JSON.stringify(opts));
var req = http.request(opts);
function onResponse(res) {
grunt.log.debug('Server responded with', res.statusCode);
var app = res.headers['x-app-name'];
if (res.statusCode === 200 && app && app === 'kibana') {
grunt.log.ok('Kibana server already started on port', options.port);
} else {
grunt.log.error('Another server is already running on port', options.port);
process.exit(1);
}
done(res);
}
function onError(err) {
if (err.code !== 'ECONNREFUSED') {
grunt.log.error('Kibana server check failed', err);
}
grunt.config.set(options.name, true);
grunt.task.run(options.tasks);
done();
}
var done = (function (cb) {
return function (res) {
req.removeListener('error', onError);
req.removeListener('response', onResponse);
if (res) res.socket.destroy();
cb();
};
})(this.async());
req.on('error', onError);
req.on('response', onResponse);
req.end();
};
};
grunt.registerTask('maybe_start_kibana', maybeStartServer({
name: 'kibana-server',
port: grunt.option('port') || config.kibana.port,
tasks: ['kibana_server']
}));
};
可以看到,在onError
函数里,才有grunt.task.run(options.tasks)
这句启动kibana server的代码。其实这句代码是在执行kibana_server
任务。
kibana_server
任务定义如下:
module.exports = function (grunt) {
grunt.registerTask('kibana_server', function (keepalive) {
var done = this.async();
var config = require('../src/server/config');
config.quiet = !grunt.option('debug') && !grunt.option('verbose');
if (grunt.option('port')) {
config.port = config.kibana.port = grunt.option('port');
}
var server = require('../src/server');
server.start(function (err) {
if (err) return done(err);
grunt.log.ok('Server started on port', config.kibana.port);
if (keepalive !== 'keepalive') done();
});
});
};
可以看到require('../src/server')
执行了目录src/server
目录下的index.js
文件,该文件里导出了server
模块对象,最终server.start
函数被调用。至此,kibana服务被启动。
最后一个watch
任务,是由[grunt-contrib-watch](https://github.com/gruntjs/grunt-contrib-watch)
插件负责执行的。该插件的功能在于监听指定文件模式的变更(新增、改变与删除),在发生变更后执行预先定义好的任务。kibana里主要是利用该插件来监听less文件与jade文件的变更,以便于文件变更后可立即执行对应的编译任务。
8.2 执行:grunt test —— 产品集成测试
kibana的测试任务里,除了像dev那样使用less编译css文件之外,还包括以下两项工作:
- 使用了
[jshint](http://jshint.com/)
与[jscs](http://jscs.info/)
两个代码校验检查工具对Javascript源码进行了校验检查; - 基于
[mocha](http://mochajs.org/)
Javascript测试框架管理并执行所有的测试用例。
(需要说明的是,这些工作内容里有众多依赖到linux/unix系统环境之处,windows平台上直接执行默认设置是不行的。)
8.3 执行:grunt build —— 产品构建与发布
kibana的build任务定义如下:
module.exports = function (grunt) {
grunt.registerTask('build', [
'get_build_props',
'clean:target',
'clean:build',
'require_css_deps:copy',
'less:build',
'copy:kibana_src',
'clean:dev_only_plugins',
'touch_config',
'replace:build_props',
'requirejs',
'clean:unneeded_source_in_build',
'copy:server_src',
'replace_package_json',
'replace:dist',
'copy:dist',
'copy:deps',
'compile_dist_readme',
'chmod_kibana',
'make_plugin_dir',
'copy:plugin_readme',
'describe_bundled_plugins',
'copy:shrinkwrap',
'npm_install_kibana',
'clean:test_from_node_modules',
'download_node_binaries',
'copy:versioned_dist',
'create_services',
'create_packages',
'create_shasums'
]);
};
build包括了更多的子任务,这其中概括地说,主要包括以下方面内容:
- 发布目录和文件清理:
- CSS编译
- 文件合并与拷贝
- 代码压缩
- 准备部署脚本
- 准备运行时环境
- 生成文档校验信息
- ……等等……
(需要说明的是,这些工作内容里有众多依赖到linux/unix系统环境之处,windows平台上直接执行默认设置是不行的。)
以下为主要任务内容概要:
clean:target
:清理target目录。clean:build
:清理build目录。require_css_deps
:将'css-builder.js', 'normalize.js' 两文件分别拷贝到 bulid/src 下与 src/kibana/bowerComponentsDir/require-css 下,为css的normalize工作服务。less:build
:编译less文件,与less:dev的区别主要在于不生成sourcemap。copy:kibana_src
:将src/kibana目录内容拷贝到build/src中。clean:dev_only_plugins
:将刚拷贝的build/src/plugins目录中只需在开发期使用的插件删除掉。这里其实只是删除了一个叫做vis_debug_spy的开发期调试插件。build_props
:针对src/kibana/index.html替换其中的构建号、版本号信息,并将替换结果输出到build/src/index.html。requirejs
:使用r.js对前端文件进行压缩优化。将build/src下的内容复制到build/kibana/public中,并且把所有的脚本都整合到index.js中去。具体合并清单可见build/kibana/public/build.txt。clean:unneeded_source_in_build
:清理掉一些在发布版本中并不要的源码,包括build/kibana/public/bower_components下的部分大部分内容,还有build/kibana/public下的less文件,以及已经使用过的css-builder.js与normalize.js文件。copy:server_src
:主要是将package.json拷贝到build/kibana目录,然后将src/server中的内容拷贝到build/kibana目录下,包括app.js与index.js入口文件与config bin lib routes views等子目录。replace_package_json
:重写了package.json文件,去掉了开发期依赖信息,添加了构建版本号码与文件校验信息。replace:dist
:主要是针对src/server/bin/kibana.sh src/server/bin/kibana.bat build/src/server/config/index.js 替换其中的构建号、版本号信息并将结果分别输出至build/dist/kibana/bin/kibana build/dist/kibana/bin/kibana.bat build/dist/kibana/src/config/index.js 。copy:dist
:将build/kibana下的所有文件拷贝到build/dist/kibana/src中,将src/server/config/kibana.yml拷贝到build/dist/kibana/config中。copy:deps
:将src/kibana/bowerComponentsDir/ace-builds/src-noconflict/worker-json.js拷贝到build/dist/kibana/src/public中。compile_dist_readme
:将README.md与LICENSE.md编译为普通文本文件,输出至build/dist/kibana/README.txt与build/dist/kibana/LICENSE.txt。chmod_kibana
:修改build/dist/kibana/bin/kibana文件的文件权限。make_plugin_dir
:任务定义在plugins.js文件中。创建了build/dist/kibana/plugins目录。copy:plugin_readme
:将build/kibana/public/plugins/README.txt拷贝到build/dist/kibana/plugins/README.txt。describe_bundled_plugins
:任务定义在plugins.js文件中。在build/dist/kibana/config/kibana.yml中添加以下插件描述列表bundled_plugin_ids。clean:test_from_node_modules
:删除build/dist/kibana/src/node_modules中测试相关的文件。download_node_binaries
:下载node.js运行时。create_services
:在系统中安装kibana服务。create_packages
:打包发布版本到target目录中。create_shasums
:为打包文件生成校验码。