Cordova CLI源碼分析(四)——建立工程

在第一篇分析咱們曾經舉例,建立一個新工程,javascript

cordova create hello hellotest com.xxx.hellotesthtml

cli.js文件分析命令行參數後,會走到java

 else if (cmd == 'create' || cmd == 'serve') {android

            cordova[cmd].apply(this, tokens);ios

        }git

將會執行create函數web

 

create.jsshell

 

var path          = require('path'),
    fs            = require('fs'),
    shell         = require('shelljs'),
    platforms     = require('../platforms'),
    help          = require('./help'),
    events        = require('./events'),
    config        = require('./config'),
    lazy_load     = require('./lazy_load'),
    util          = require('./util');

var DEFAULT_NAME = "HelloCordova",
    DEFAULT_ID   = "io.cordova.hellocordova";

/**
 * Usage:
 * create(dir) - creates in the specified directory
 * create(dir, name) - as above, but with specified name
 * create(dir, id, name) - you get the gist
 **/
module.exports = function create (dir, id, name, callback) {
    var options = [];

    if (arguments.length === 0) {
        return help();//src/help.js  讀取doc/help.txt內容,在終端顯示幫助信息
    }

    // Massage parameters
    var args = Array.prototype.slice.call(arguments, 0);
    //arguments不是數組,可是能夠經過arguments.length取到長度
    //Array.prototype.slice.call(arguments,0)就相似於arguments.slice(0),
    //但由於arguments不是真正的Array,因此它沒有slice這個方法.能用slice方法的,只要有length屬性就行。
    //Array.prototype已經被call改爲arguments了,由於知足slice執行的條件(有length屬性),因此沒有報錯。
    //簡單說做用就是:把arguments這個僞數組轉換爲真正的數組
    if (typeof args[args.length-1] == 'function') {
        callback = args.pop();
    } else if (typeof callback !== 'function') {
        callback = undefined;
    }
    //判斷是否有回調函數
    

    if (args.length === 0) {
        dir = process.cwd();//得到當前路徑
        id = DEFAULT_ID;
        name = DEFAULT_NAME;
    } else if (args.length == 1) {
        id = DEFAULT_ID;
        name = DEFAULT_NAME;
    } else if (args.length == 2) {
        name = DEFAULT_NAME;
    } else {
        dir = args.shift();//shift() 方法用於把數組的第一個元素從其中刪除,並返回第一個元素的值
        id = args.shift();
        name = args.shift();
        options = args;
    }

    // Make absolute.
    dir = path.resolve(dir);

    events.emit('log', 'Creating a new cordova project with name "' + name + '" and id "' + id + '" at location "' + dir + '"');

    var dotCordova = path.join(dir, '.cordova');
    var www_dir = path.join(dir, 'www');

    // Create basic project structure.
    shell.mkdir('-p', dotCordova);
    shell.mkdir('-p', path.join(dir, 'platforms'));
    shell.mkdir('-p', path.join(dir, 'merges'));
    shell.mkdir('-p', path.join(dir, 'plugins'));
    shell.mkdir('-p', www_dir);
    var hooks = path.join(dotCordova, 'hooks');
    shell.mkdir('-p', hooks);
    //建立一系列工做目錄
    //當前dir/.cordova , dir/www , dir/platforms, dir/merges ,dir/plugins ,dir/.cordova/hooks 
    

    // Add directories for hooks
    shell.mkdir(path.join(hooks, 'after_build'));
    shell.mkdir(path.join(hooks, 'after_compile'));
    shell.mkdir(path.join(hooks, 'after_docs'));
    shell.mkdir(path.join(hooks, 'after_emulate'));
    shell.mkdir(path.join(hooks, 'after_platform_add'));
    shell.mkdir(path.join(hooks, 'after_platform_rm'));
    shell.mkdir(path.join(hooks, 'after_platform_ls'));
    shell.mkdir(path.join(hooks, 'after_plugin_add'));
    shell.mkdir(path.join(hooks, 'after_plugin_ls'));
    shell.mkdir(path.join(hooks, 'after_plugin_rm'));
    shell.mkdir(path.join(hooks, 'after_prepare'));
    shell.mkdir(path.join(hooks, 'after_run'));
    shell.mkdir(path.join(hooks, 'before_build'));
    shell.mkdir(path.join(hooks, 'before_compile'));
    shell.mkdir(path.join(hooks, 'before_docs'));
    shell.mkdir(path.join(hooks, 'before_emulate'));
    shell.mkdir(path.join(hooks, 'before_platform_add'));
    shell.mkdir(path.join(hooks, 'before_platform_rm'));
    shell.mkdir(path.join(hooks, 'before_platform_ls'));
    shell.mkdir(path.join(hooks, 'before_plugin_add'));
    shell.mkdir(path.join(hooks, 'before_plugin_ls'));
    shell.mkdir(path.join(hooks, 'before_plugin_rm'));
    shell.mkdir(path.join(hooks, 'before_prepare'));
    shell.mkdir(path.join(hooks, 'before_run'));

    // Write out .cordova/config.json file with a simple json manifest
    //使用指定id和name參數寫入config.json
    require('../cordova').config(dir, {
        id:id,
        name:name
    });

    var config_json = config.read(dir);
    //讀取.cordova/config.json
    
    //finalize函數做用:判斷下載的tar包是否完整,刪除原有www目錄下內容,拷貝新內容到www目錄,添加配置文件
    var finalize = function(www_lib) {
        while (!fs.existsSync(path.join(www_lib, 'index.html'))) {
            www_lib = path.join(www_lib, 'www');
            if (!fs.existsSync(www_lib)) {
                var err = new Error('downloaded www assets in ' + www_lib + ' does not contain index.html, or www subdir with index.html');
                if (callback) return callback(err);
                else throw err;
            }
        }
        //刪除當前www目錄下因此文件
        shell.cp('-rf', path.join(www_lib, '*'), www_dir);
        var configPath = util.projectConfig(dir);
        //得到config.xml路徑 www/config.xml
        
        // Add template config.xml for apps that are missing it
        if (!fs.existsSync(configPath)) {
            var template_config_xml = path.join(__dirname, '..', 'templates', 'config.xml');
            shell.cp(template_config_xml, www_dir);
        }
        //若是config.xml不存在,從cli模板目錄拷貝此文件到當前應用目錄
        
        // Write out id and name to config.xml  id和name寫入config.xml
        var config = new util.config_parser(configPath);
        config.packageName(id);
        config.name(name);
        if (callback) callback();
    };

    // Check if www assets to use was overridden.
    if (config_json.lib && config_json.lib.www) {
    	//判斷config.josn的lib和www字段是否爲空
        events.emit('log', 'Using custom www assets ('+config_json.lib.www.id+').');
        //下載config.json中uri地址的hello-world文件包
        lazy_load.custom(config_json.lib.www.uri, config_json.lib.www.id, 'www', config_json.lib.www.version, function(err) {
            if (err) {
                if (callback) callback(err);
                else throw err;
            } else {
                events.emit('log', 'Copying custom www assets into "' + www_dir + '"');
                //下載成功後拷貝到指定目錄
                finalize(path.join(util.libDirectory, 'www', config_json.lib.www.id, config_json.lib.www.version));
            }
        });
    } else {
        // Nope, so use stock cordova-hello-world-app one.
        events.emit('log', 'Using stock cordova hello-world application.');
        //根據platform.js中參數下載默認文件
        lazy_load.cordova('www', function(err) {
            if (err) {
                if (callback) callback(err);
                else throw err;
            } else {
                events.emit('log', 'Copying stock Cordova www assets into "' + www_dir + '"');
                finalize(path.join(util.libDirectory, 'www', 'cordova', platforms.www.version));
            }
        });
    }
};

 

 

1)參數解析,建立工做目錄:參數存在狀況下,按照dir, id, name,這幾個參數指定內容建立目錄,並將idname參數寫入config.json文件。apache

 

hello/npm

|--.cordova/

| | -- hooks/

| | -- config.json

|-- merges/

|-- www/

| `-- config.xml

|-- platforms/

`-- plugins/

在.cordova/hooks目錄下建立了不少以事件名稱命名的目錄,如after_build ,after_compile等,在這些目錄下,用戶能夠添加自定義文件,當這些系統事件發生時,這些目錄下的文件至關於回調函數,會被執行

這部分還涉及到config.js文件,其中定義了對config.json文件讀寫操做的函數

 

2判斷config.josnlibwww字段是否爲空,非空時,按照.cordova/config_json中指定的參數下載默認的web頁面部分文件(即www目錄下內容),下載成功後會調用finalize函數,該函數會刪除原www目錄下文件,拷貝下載的文件到www目錄。

3lazy_load 函數是提供下載功能的接口文件(src/lazy_load.js),主要包含兩個函數

lazy_load.cordova

lazy_load.custom

 

lazy_load.js

module.exports = {
    cordova:function lazy_load(platform, callback) {
        if (!(platform in platforms)) {
            var err = new Error('Cordova library "' + platform + '" not recognized.');
            if (callback) return callback(err);
            else throw err;
        }

        var url = platforms[platform].url + ';a=snapshot;h=' + platforms[platform].version + ';sf=tgz';
        module.exports.custom(url, 'cordova', platform, platforms[platform].version, function(err) {
            if (err) {
                if (callback) return callback(err);
                else throw err;
            } else {
                if (callback) callback();
            }
        });
    },
    custom:function(url, id, platform, version, callback) {
        var download_dir = (platform == 'wp7' || platform == 'wp8' ? path.join(util.libDirectory, 'wp', id, version) :
                                                                     path.join(util.libDirectory, platform, id, version));
        if (fs.existsSync(download_dir)) {
            events.emit('log', id + ' library for "' + platform + '" already exists. No need to download. Continuing.');
            if (callback) return callback();
        }
        hooker.fire('before_library_download', {
            platform:platform,
            url:url,
            id:id,
            version:version
        }, function() {
            var uri = URL.parse(url);
            if (uri.protocol && uri.protocol[1] != ':') { // second part of conditional is for awesome windows support. fuuu windows
                npm.load(function() {
                    // Check if NPM proxy settings are set. If so, include them in the request() call.
                    var proxy;
                    if (uri.protocol == 'https:') {
                        proxy = npm.config.get('https-proxy');
                    } else if (uri.protocol == 'http:') {
                        proxy = npm.config.get('proxy');
                    }

                    shell.mkdir('-p', download_dir);
                    var size = 0;
                    var request_options = {uri:url};
                    if (proxy) {
                        request_options.proxy = proxy;
                    }
                    events.emit('log', 'Requesting ' + JSON.stringify(request_options) + '...');
                    request.get(request_options, function(err, req, body) { size = body.length; })
                    .pipe(zlib.createUnzip())
                    .pipe(tar.Extract({path:download_dir}))
                    .on('error', function(err) {
                        shell.rm('-rf', download_dir);
                        if (callback) callback(err);
                        else throw err;
                    })
                    .on('end', function() {
                        events.emit('log', 'Downloaded, unzipped and extracted ' + size + ' byte response.');
                        var entries = fs.readdirSync(download_dir);
                        var entry = path.join(download_dir, entries[0]);
                        shell.mv('-f', path.join(entry, (platform=='blackberry10'?'blackberry10':''), '*'), download_dir);
                        shell.rm('-rf', entry);
                        hooker.fire('after_library_download', {
                            platform:platform,
                            url:url,
                            id:id,
                            version:version,
                            path:download_dir,
                            size:size,
                            symlink:false
                        }, function() {
                            if (callback) callback();
                        });
                    });
                });
            } else {
                // local path
                // symlink instead of copying
                fs.symlinkSync((uri.protocol && uri.protocol[1] == ':' ? uri.href : uri.path), download_dir, 'dir');
                hooker.fire('after_library_download', {
                    platform:platform,
                    url:url,
                    id:id,
                    version:version,
                    path:download_dir,
                    symlink:true
                }, function() {
                    if (callback) callback();
                });
            }
        });
    },
    based_on_config:function(project_root, platform, callback) {
        var custom_path = config.has_custom_path(project_root, platform);
        if (custom_path) {
            var dot_file = config.read(project_root);
            module.exports.custom(dot_file.lib[platform].uri, dot_file.lib[platform].id, platform, dot_file.lib[platform].version, callback);
        } else {
            module.exports.cordova(platform, callback);
        }
    }
};

 

lazy_load.cordova首先查找傳入的platform參數是否在頂層目錄下platforms.js文件中的對象參數中是否存在;

platforms.js

module.exports = {
    'ios' : {
        parser : require('./src/metadata/ios_parser'),
        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-ios.git',
        version: '3.0.0'
    }, 
    'android' : {
        parser : require('./src/metadata/android_parser'),
        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-android.git',
        version: '3.0.0'
    }, 
    'wp7' : {
        parser : require('./src/metadata/wp7_parser'),
        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-wp8.git',
        version: '3.0.0'
    },
    'wp8' : {
        parser : require('./src/metadata/wp8_parser'),
        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-wp8.git',
        version: '3.0.0'
    },
    blackberry10 : {
        parser : require('./src/metadata/blackberry10_parser'),
        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-blackberry.git',
        version: '3.0.0'
    },
    'www':{
        url    : 'https://git-wip-us.apache.org/repos/asf?p=cordova-app-hello-world.git',
        version: '3.0.0'
    }
};

 

platforms.js中列出了全部cordova支持的平臺,每一個平臺包括三個參數: 

parser : 解析平臺配置參數的解析文件位置;

url : 源碼存在的git路徑

version:版本號

lazy_load.cordova會按照傳入參數在這個表中找到對應url下載文件。

 

lazy_load.custom與lazy_load.cordova不一樣之處是,從.cordova/config_json中指定的url參數下載文件

shelljs,create函數中屢次用到shelljs,它是一個可移植的相似unix shell的命令行工具,支持Windows/Linux/OS X,詳見https://npmjs.org/package/shelljs

相關文章
相關標籤/搜索