IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    FIS源码解析-整体架构

    TAT.casperchen, TAT.casperchen发表于 2015-05-08 06:42:52
    love 0

    序言

    这里假设本文读者对FIS已经比较熟悉,如还不了解,可猛击官方文档。

    虽然FIS整体的源码结构比较清晰,不过讲解起来也是个系统庞大的工程,笔者尽量的挑重点的讲。如果读者有感兴趣的部分笔者没有提到的,或者是存在疑惑的,可以在评论里跑出来,笔者会试着去覆盖这些点。

    下笔匆忙,如有错漏请指出。

    Getting started

    如在开始剖析FIS的源码前,有三点内容首先强调下,这也是解构FIS内部设计的基础。

    1、 FIS支持三个命令,分别是fis release、fis server、fis install。当用户输入fis xx的时候,内部调用fis-command-release、fis-command-server、fis-command-install这三个插件来完成任务。同时,FIS的命令行基于commander这个插件构建,熟悉这个插件的同学很容易看懂FIS命令行相关部分源码。

    2、FIS以fis-kernel为核心。fis-kernel提供了FIS的底层能力,包含了一系列模块,如配置、缓存、文件处理、日志等。FIS的三个命令,最终调用了这些模块来完成构建的任务。参考 fis-kernel/lib/ 目录,下面对每个模块的大致作用做了简单备注,后面的文章再详细展开。

    lib/
    ├── cache.js    // 缓存模块,提高编译速度
    ├── compile.js    // (单)文件编译模块
    ├── config.js  // 配置模块,fis.config 
    ├── file.js  // 文件处理
    ├── log.js // 日志
    ├── project.js  // 项目相关模块,比如获取、设置项目构建根路径、设置、获取临时路径等
    ├── release.js  // fis release 的时候调用,依赖 compile.js 完成单文件编译。同时还完成如文件打包等任务。├── uri.js  // uri相关
    └── util.js  // 各种工具函数

    3、FIS的编译过程,最终可以拆解为细粒度的单文件编译,理解了下面这张图,对于阅读FIS的源码有非常大的帮助。(主要是fis release这个命令)

    enter image description here

    一个简单的例子:fis server open

    开篇的描述可能比较抽象,下面我们来个实际的例子。通过这个简单的例子,我们可以对FIS的整体设计有个大致的印象。

    下文以fis server open为例,逐步剖析FIS的整体设计。其实FIS比较精华的部分集中在fis release这个命令,不过fis server这个命令相对简单,更有助于我们从纷繁的细节中跳出来,窥探FIS的整体概貌。

    假设我们已经安装了FIS。好,打开控制台,输入下面命令,其实就是打开FIS的server目录

    fis server open

    从package.json可以知道,此时调用了 fis/bin/fis,里面只有一行有效代码,调用fis.cli.run()方法,同时将进程参数传进去。

    #!/usr/bin/env node
    
    require('../fis.js').cli.run(process.argv);

    接下来看下../fis.js。代码结构非常清晰。注意,笔者将一些代码给去掉,避免长串的代码影响理解。同时在关键处加了简单的注释

    // 加载FIS内核
    var fis = module.exports = require('fis-kernel');
    
    //项目默认配置
    fis.config.merge({
       // ...
    });
    
    //exports cli object
    // fis命令行相关的对象
    fis.cli = {};
    
    // 工具的名字。在基于fis的二次解决方案中,一般会将名字覆盖
    fis.cli.name = 'fis';
    
    //colors
    // 日志友好的需求
    fis.cli.colors = require('colors');
    
    //commander object
    // 其实最后就挂载了 commander 这个插件
    fis.cli.commander = null;
    
    //package.json
    // 把package.json的信息读进来,后面会用到
    fis.cli.info = fis.util.readJSON(__dirname + '/package.json');
    
    //output help info
    // 打印帮助信息的API
    fis.cli.help = function(){
        // ...
    };
    
    // 需要打印帮助信息的命令,在 fis.cli.help() 中遍历到。 如果有自定义命令,并且同样需要打印帮助信息,可以覆盖这个变量
    fis.cli.help.commands = [ 'release', 'install', 'server' ];
    
    //output version info
    // 打印版本信息
    fis.cli.version = function(){
        // ...
    };
    
    // 判断是否传入了某个参数(search)
    function hasArgv(argv, search){
        // ...
    }
    
    //run cli tools
    // 核心方法,构建的入口所在。接下来我们就重点分析下这个方法。假设我们跑的命令是 fis server open
    // 实际 process.argv为 [ 'node', '/usr/local/bin/fis', 'server', 'open' ]
    // 那么,argv[2] ==> 'server'
    fis.cli.run = function(argv){
        // ...
    };

    我们来看下笔者注释过的fis.cli.run的源码。

    1. 如果是fis -h或者fis --help,打印帮助信息
    2. 如果是fis -v或者fis --version,打印版本信息
    3. 其他情况:加载相关命令对应的插件,并执行命令,比如 fis-command-server

    //run cli tools
    fis.cli.run = function(argv){
        
        fis.processCWD = process.cwd(); // 当前构建的路径
    
        if(hasArgv(argv, '--no-color')){    // 打印的命令行是否单色
            fis.cli.colors.mode = 'none';
        }
    
        var first = argv[2];
        if(argv.length < 3 || first === '-h' ||  first === '--help'){
            fis.cli.help(); // 打印帮助信息
        } else if(first === '-v' || first === '--version'){
            fis.cli.version();  // 打印版本信息
        } else if(first[0] === '-'){
            fis.cli.help(); // 打印版本信息
        } else {
            //register command
            // 加载命令对应的插件,这里特指 fis-command-server
            var commander = fis.cli.commander = require('commander');
            var cmd = fis.require('command', argv[2]);
            cmd.register(
                commander
                    .command(cmd.name || first)
                    .usage(cmd.usage)
                    .description(cmd.desc)
            );
            commander.parse(argv);  // 执行命令
        }
    };

    通过fis.cli.run的源码,我们可以看到,fis-command-xx插件,都提供了register方法,在这个方法内完成命令的初始化。之后,通过commander.parse(argv)来执行命令。

    整个流程归纳如下:

    1. 用户输入FIS命令,如fis server open
    2. 解析命令,根据指令加载对应插件,如fis-command-server
    3. 执行命令

    fis-command-server源码

    三个命令相关的插件中,fis-command-server的代码比较简单,这里就通过它来大致介绍下。

    根据惯例,同样是抽取一个超级精简版的fis-command-server,这不影响我们对源码的理解

    var server = require('./lib/server.js');  // 依赖的基础库
    
    // 命令的配置属性,打印帮助信息的时候会用到
    exports.name = 'server';
    exports.usage = '<command> [options]';
    exports.desc = 'launch a php-cgi server';
    
    // 对外暴露的 register 方法,参数的参数为 fis.cli.command 
    exports.register = function(commander) {
        
        // 略过若干个函数
    
        // 命令的可选参数,格式参考 commander 插件的文档说明
        commander
            .option('-p, --port <int>', 'server listen port', parseInt, process.env.FIS_SERVER_PORT || 8080)      
            .action(function(){
                // 当 command.parse(..)被调用时,就会进入这个回调方法。在这里根据fis server 的子命令执行具体的操作
                // ...
            });
      
        // 注册子命令 fis server open
        // 同理,可以注册 fis server start 等子命令
        commander
            .command('open')
            .description('open document root directory');
    };

    好了,fis server open 就大致剖析到这里。只要熟悉commander这个插件,相信不难看懂上面的代码,这里就不多做展开了,有空也写篇科普文讲下commander的使用。

    写在后面

    如序言所说,欢迎交流探讨。如有错漏,请指出。

    文章: casperchen



沪ICP备19023445号-2号
友情链接