zoukankan      html  css  js  c++  java
  • Cordova CLI源码分析(四)——创建工程

    在第一篇分析我们曾经举例,创建一个新工程,

    cordova create hello hellotest com.xxx.hellotest

    cli.js文件分析命令行参数后,会走到

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

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

            }

    将会执行create函数

    create.js

    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文件。

    hello/

    |--.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


  • 相关阅读:
    Web Api系列教程第2季(OData篇)(一)——OData简介和一个小应用
    漫步ASP.NET MVC的处理管线
    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【十】——使用CacheCow和ETag缓存资源
    工具分享——将C#文档注释生成.chm帮助文档
    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【外传】——Attribute Routing
    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【九】——API变了,客户端怎么办?
    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【八】——Web Api的安全性
    C#基础——谈谈.NET异步编程的演变史
    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【七】——实现资源的分页
    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【六】——实现资源间的关联
  • 原文地址:https://www.cnblogs.com/riskyer/p/3289824.html
Copyright © 2011-2022 走看看