如果我想在一个独立的 JS必威: 文件里使用,里

仙剑奇侠传的web移植版

2015/10/06 · HTML5 · 1 评论 · 仙剑奇侠传

初稿出处: 刘骥(@刘骥-JimLiu)   

前言

API完毕阶段之JS端的完成,爱戴描述那些类型的JS端都有些什么内容,是什么样贯彻的。

不一致于一般混合框架的只富含JSBridge部分的前端实现,本框架的前端达成蕴含JSBridge部分、多平台辅助,统一预管理等等。

前言

API实现阶段之JS端的实现,重视描述这一个类型的JS端都有个别什么内容,是如何促成的。

差别于一般混合框架的只含有JSBridge部分的前端完毕,本框架的前端实现富含JSBridge部分、多平台支撑,统一预管理等等。

摘录

相信大家都试过在二个 View 里嵌套使用 javascript,那时就足以平素利用 Razor 语法以调用 .NET 的部分主意。如以下代码嵌套在八个 Razor 的 View 里:

0. 前言

那是二个坑了太久太久的类型,久到自己早已不记得挖这几个坑是何许时候了。大约是13年的夏日呢,作者挖了这么些坑,然后信心满满的在那时十一长假宅了N天(小编还比较清楚的回忆那时候便是WOW开垦围攻奥格瑞玛别本的阶段),写下了百分之百框架,以及最中央的一片段代码,然后,就从未有过然后了。

概略一年后,作者又翻出来了那些坑,重构了汪洋的代码,但是速度差不离从未实质性的发展,以致因为重构而全部倒退- -“,可是因为读了《游戏引擎架构》那本书,作者对那些坑又有了新的认知,对于那个程序到底要怎么写心里有谱多了。

理当如此陈设是在当年夏季搞出来,那样能够遇到仙剑20周年(1992年十二月)发表,然而并不是想也晓得肯定是继续坑了。

磕磕绊绊到现行反革命,总算是把嬉戏的完全完毕度拉到了一个相比能见人的水准,于是本人以为照旧赶紧公布的好,免得又变有生之年了。

项目标结构

在最先的本子中,其实任何前端库就独有二个文本,里面只明显着怎么样完结JSBridge和原生交互部分。不过到新型的版本中,由于效果日益充实,单一文件难以满意须要和爱戴,由此重构成了一整个项目。

一切项目基于ES6Airbnb代码规范,使用gulp + rollup创设,部分入眼代码实行了Karma + Mocha单元测量试验

全部目录结构如下:

quickhybrid
    |- dist             // 发布目录
    |   |- quick.js
    |   |- quick.h5.js
    |- build            // 构建项目的相关代码
    |   |- gulpfile.js
    |   |- rollupbuild.js
    |- src              // 核心源码
    |   |- api          // 各个环境下的api实现 
    |   |   |- h5       // h5下的api
    |   |   |- native   // quick下的api
    |   |- core         // 核心控制
    |   |   |- ...      // 将核心代码切割为多个文件
    |   |- inner        // 内部用到的代码
    |   |- util         // 用到的工具类
    |- test             // 单元测试相关
    |   |- unit         
    |   |   |- karma.xxx.config.js
    |   |- xxx.spec.js
    |   |- ...

必威 1

花色的布局

在最先的本子中,其实全数前端库就只有三个文书,里面只规定着什么完成JSBridge和原生交互部分。不过到新型的版本中,由于效果稳步增添,单一文件难以知足须求和掩护,因而重构成了一整个项目。

全体项目基于ES6Airbnb代码规范,使用gulp + rollup创设,部分最主要代码实行了Karma + Mocha单元测量检验

全部目录结构如下:

quickhybrid
    |- dist             // 发布目录
    |   |- quick.js
    |   |- quick.h5.js
    |- build            // 构建项目的相关代码
    |   |- gulpfile.js
    |   |- rollupbuild.js
    |- src              // 核心源码
    |   |- api          // 各个环境下的api实现 
    |   |   |- h5       // h5下的api
    |   |   |- native   // quick下的api
    |   |- core         // 核心控制
    |   |   |- ...      // 将核心代码切割为多个文件
    |   |- inner        // 内部用到的代码
    |   |- util         // 用到的工具类
    |- test             // 单元测试相关
    |   |- unit         
    |   |   |- karma.xxx.config.js
    |   |- xxx.spec.js
    |   |- ...

必威 2

小说首要是介绍了通过八个第三方类库RazorJS,完结Javascript 文件里使用 .Net MVC Razor 语法,很巧妙,推荐给我们

<script>
 var currDate = '@DateTime.Now'; //直接调用.NET的方法

 console.log(currDate)
</script>

1. 无图言屌

优酷录制——有摄像有JB!

必威 3必威 4

必威 5

必威 6

必威 7

必威 8

必威 9

必威 10

必威 11

代码架构

品种代军长宗旨代码和API达成代码分开,大旨代码约等于一个管理引擎,而相继境遇下的两样API达成能够独自挂载(这里是为了方便另外地点结合差别情形下的API所以才分开的,实际上能够将native和大旨代码打包到一齐)

quick.js
quick.h5.js
quick.native.js

此间供给专一,quick.xx环境.js中的代码是根据quick.js基本代码的(举例里面需求选择一些特色的高速调用底层的不二秘籍)

而内部最中央的quick.js代码架构如下

index
    |- os               // 系统判断相关
    |- promise          // promise支持,这里并没有重新定义,而是判断环境中是否已经支持来决定是否支持
    |- error            // 统一错误处理
    |- proxy            // API的代理对象,内部对进行统一预处理,如默认参数,promise支持等
    |- jsbridge         // 与native环境下原生交互的桥梁
    |- callinner        // API的默认实现,如果是标准的API,可以不传入runcode,内部默认采用这个实现
    |- defineapi        // API的定义,API多平台支撑的关键,也约定着该如何拓展
    |- callnative       // 定义一个调用通用native环境API的方法,拓展组件API(自定义)时需要这个方法调用
    |- init             // 里面定义config,ready,error的使用
    |- innerUtil        // 给核心文件绑定一些内部工具类,供不同API实现中使用

能够见到,焦点代码已经被切割成十分的小的单元了,即便说最终包装起来总共代码也未尝多少,可是为了维护性,简洁性,这种拆分还是很有不可缺少的

代码架构

项目代中将主题代码和API达成代码分开,主旨代码也正是三个甩卖引擎,而种种蒙受下的两样API实现可以独自挂载(这里是为着便利另外地点结合分裂条件下的API所以才分开的,实际上能够将native和宗旨代码打包到共同)

quick.js
quick.h5.js
quick.native.js

此间要求小心,quick.xx环境.js中的代码是基于quick.js骨干代码的(举个例子里面要求使用一些特点的高速调用底层的方式)

而其中最中央的quick.js代码架构如下

index
    |- os               // 系统判断相关
    |- promise          // promise支持,这里并没有重新定义,而是判断环境中是否已经支持来决定是否支持
    |- error            // 统一错误处理
    |- proxy            // API的代理对象,内部对进行统一预处理,如默认参数,promise支持等
    |- jsbridge         // 与native环境下原生交互的桥梁
    |- callinner        // API的默认实现,如果是标准的API,可以不传入runcode,内部默认采用这个实现
    |- defineapi        // API的定义,API多平台支撑的关键,也约定着该如何拓展
    |- callnative       // 定义一个调用通用native环境API的方法,拓展组件API(自定义)时需要这个方法调用
    |- init             // 里面定义config,ready,error的使用
    |- innerUtil        // 给核心文件绑定一些内部工具类,供不同API实现中使用

能够见见,大旨代码已经被切割成极小的单元了,即使说最终包装起来一共代码也从未多少,然则为了维护性,简洁性,这种拆分照旧很有不可缺少的

信任我们都试过在二个 View 里嵌套使用 javascript,那时就可以直接利用 Razor 语法以调用 .NET 的片段主意。如以下代码嵌套在二个 Razor 的 View 里:

但另一种情形是,就算小编想在三个单身的 JS 文件里使用 Razor,那以上的格局可不行,因为MVC不会一向表达JS文件,唯有放到 Razor view里技艺够。可是在此我向大家推荐多个第三方类库,就可令你间接在独立的 JS 文件里采用 Razor

2. 自问自答的FAQ

集结的预管理

在上一篇API多平台的支撑中有涉嫌怎么样依照Object.defineProperty贯彻多个支撑多平台调用的API,完毕起来的API大概是这样子的

Object.defineProperty(apiParent, apiName, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 确保get得到的函数一定是能执行的
        const nameSpaceApi = proxysApis[finalNameSpace];

        // 得到当前是哪一个环境,获得对应环境下的代理对象
        return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
    },
    set: function proxySetter() {
        alert('不允许修改quick API');
    },
});

...

quick.extendModule('ui', [{
    namespace: 'alert',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(message) {
        alert('h5-' + message);
    },
}]);

其中nameSpaceApi.h5的值是api.runCode,也正是说直接实行runCode(...)中的代码

无非那样是缺乏的,大家必要对调用方法的输入等做联合预管理,因而在那边,我们依照实际的境况,在此基础上极其健全,加上统一预处理机制,也就是

const newProxy = new Proxy(api, apiRuncode);

Object.defineProperty(apiParent, apiName, {
    ...
    get: function proxyGetter() {
        ...
        return newProxy.walk();
    }
});

咱俩将新的周转代码变为三个代理对象Proxy,代理api.runCode,然后在get时重临代理过后的莫过于措施(.walk()措施表示代理对象内部会进展贰遍联合的预管理)

代理对象的代码如下

function Proxy(api, callback) {
    this.api = api;
    this.callback = callback;
}

Proxy.prototype.walk = function walk() {
    // 实时获取promise
    const Promise = hybridJs.getPromise();

    // 返回一个闭包函数
    return (...rest) = >{
        let args = rest;

        args[0] = args[0] || {};
        // 默认参数的处理
        if (this.api.defaultParams && (args[0] instanceof Object)) {
            Object.keys(this.api.defaultParams).forEach((item) = >{
                if (args[0][item] === undefined) {
                    args[0][item] = this.api.defaultParams[item];
                }
            });
        }

        // 决定是否使用Promise
        let finallyCallback;

        if (this.callback) {
            // 将this指针修正为proxy内部,方便直接使用一些api关键参数
            finallyCallback = this.callback;
        }

        if (Promise) {
            return finallyCallback && new Promise((resolve, reject) = >{
                // 拓展 args
                args = args.concat([resolve, reject]);
                finallyCallback.apply(this, args);
            });
        }

        return finallyCallback && finallyCallback.apply(this, args);
    };
};

从源码中能够看出,这么些代理对象统一预管理了两件事情:

  • 1.对此官方的输入参数,进行暗许参数的卓越

  • 2.假诺条件中援助Promise,那么重临Promise对象何况参数的终极加上resolvereject

与此同一时间,后续要是有新的统一预管理(调用API前的预管理),只需在这么些代理对象的那一个法子中加进就可以

统一的预管理

在上一篇API多平台的支撑中有提到怎么样依据Object.defineProperty福衢寿车三个支撑多平台调用的API,实现起来的API大约是那样子的

Object.defineProperty(apiParent, apiName, {
    configurable: true,
    enumerable: true,
    get: function proxyGetter() {
        // 确保get得到的函数一定是能执行的
        const nameSpaceApi = proxysApis[finalNameSpace];

        // 得到当前是哪一个环境,获得对应环境下的代理对象
        return nameSpaceApi[getCurrProxyApiOs(quick.os)] || nameSpaceApi.h5;
    },
    set: function proxySetter() {
        alert('不允许修改quick API');
    },
});

...

quick.extendModule('ui', [{
    namespace: 'alert',
    os: ['h5'],
    defaultParams: {
        message: '',
    },
    runCode(message) {
        alert('h5-' + message);
    },
}]);

其中nameSpaceApi.h5的值是api.runCode,约等于说直接实施runCode(...)中的代码

无非那样是非常不足的,我们必要对调用方法的输入等做统一预管理,因而在此地,我们根据实际的情形,在此基础上特别完善,加上统一预处理机制,也就是

const newProxy = new Proxy(api, apiRuncode);

Object.defineProperty(apiParent, apiName, {
    ...
    get: function proxyGetter() {
        ...
        return newProxy.walk();
    }
});

笔者们将新的周转代码变为多个代理对象Proxy,代理api.runCode,然后在get时回来代理过后的莫过于方法(.walk()艺术表示代理对象内部会议及展览开一回联合的预管理)

代理对象的代码如下

function Proxy(api, callback) {
    this.api = api;
    this.callback = callback;
}

Proxy.prototype.walk = function walk() {
    // 实时获取promise
    const Promise = hybridJs.getPromise();

    // 返回一个闭包函数
    return (...rest) = >{
        let args = rest;

        args[0] = args[0] || {};
        // 默认参数的处理
        if (this.api.defaultParams && (args[0] instanceof Object)) {
            Object.keys(this.api.defaultParams).forEach((item) = >{
                if (args[0][item] === undefined) {
                    args[0][item] = this.api.defaultParams[item];
                }
            });
        }

        // 决定是否使用Promise
        let finallyCallback;

        if (this.callback) {
            // 将this指针修正为proxy内部,方便直接使用一些api关键参数
            finallyCallback = this.callback;
        }

        if (Promise) {
            return finallyCallback && new Promise((resolve, reject) = >{
                // 拓展 args
                args = args.concat([resolve, reject]);
                finallyCallback.apply(this, args);
            });
        }

        return finallyCallback && finallyCallback.apply(this, args);
    };
};

从源码中可以观察,那些代理对象统一预管理了两件业务:

  • 1.对于合法的输入参数,实行暗中认可参数的同盟

  • 2.比方条件中援救Promise,那么重临Promise对象并且参数的末段加上resolvereject

与此同期,后续假使有新的联结预处理(调用API前的预管理),只需在这么些代理对象的那么些法子中扩大就能够

?

此类库名字就叫 RazorJS,那是一个开源的类别,可到以下地点下载源码:

2.1. 能玩吗?

。但在GitHub repo里并不会含有游戏的能源文件,于是须要本人去找(嘿嘿mq2x)。由于不散发游戏能源文件,且思虑到体量,作者也不会提供贰个在线娱乐的版本。所以基本上只有开垦者大概动手才具强的校友才干玩上它了(假诺您确实想玩……)

不思量曰镪BUG(无数个)造成游戏一贯罢工的状态下(当然正是小编的本人是能够轻车熟路地避过那么些BUG的233333),曾经得以从新开游戏一向玩到大结局了,何况本身一度通过海关两三回了XD

JSBridge深入分析准则

日前的篇章中有提到JSBridge的达成,但那时其实越来越多的是关切原理层面,那么实际上,定义的相互深入分析法规是何等的吗?如下

// 以ui.toast实际调用的示例
// `${CUSTOM_PROTOCOL_SCHEME}://${module}:${callbackId}/${method}?${params}`
const uri = 'QuickHybridJSBridge://ui:9527/toast?{"message":"hello"}';

if (os.quick) {
    // 依赖于os判断
    if (os.ios) {
        // ios采用
        window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(uri);
    } else {
        window.top.prompt(uri, '');
    }
} else {
    // 浏览器
    warn(`浏览器中jsbridge无效, 对应scheme: ${uri}`);
}

原生容器中收到到对于的uri后反分析就可以知道调用了些什么,上述中:

  • QuickHybridJSBridge是本框架交互的scheme标记

  • modulemethod分别表示API的模块名和情势名

  • params是对此艺术传递的附加参数,原生容器会深入分析成JSONObject

  • callbackId是此番API调用在H5端的回调id,原生容器推行完后,文告H5时会传递回调id,然后H5端找到相应的回调函数并试行

干什么要用uri的诀窍,因为这种艺术得以相配之前的scheme格局,借使方案切换,变动代价下(本人正是如此升级上来的,所以并未替换的不可或缺)

JSBridge分析准则

前方的稿子中有关系JSBridge的贯彻,但当下其实更加多的是关爱原理层面,那么实际上,定义的竞相深入分析准则是哪些的吧?如下

// 以ui.toast实际调用的示例
// `${CUSTOM_PROTOCOL_SCHEME}://${module}:${callbackId}/${method}?${params}`
const uri = 'QuickHybridJSBridge://ui:9527/toast?{"message":"hello"}';

if (os.quick) {
    // 依赖于os判断
    if (os.ios) {
        // ios采用
        window.webkit.messageHandlers.WKWebViewJavascriptBridge.postMessage(uri);
    } else {
        window.top.prompt(uri, '');
    }
} else {
    // 浏览器
    warn(`浏览器中jsbridge无效, 对应scheme: ${uri}`);
}

原生容器中接收到对于的uri后反解析即可见道调用了些什么,上述中:

  • QuickHybridJSBridge是本框架交互的scheme标志

  • modulemethod独家表示API的模块名和措施名

  • params是对此艺术传递的附加参数,原生容器会深入分析成JSONObject

  • callbackId是本次API调用在H5端的回调id,原生容器施行完后,布告H5时会传递回调id,然后H5端找到相应的回调函数并实行

缘何要用uri的艺术,因为这种办法得以合营之前的scheme方式,假若方案切换,变动代价下(本人正是那样进级上来的,所以并没有替换的须求)

1
2
3
4
5
<script>
 var currDate = '@DateTime.Now'; //直接调用.NET的方法
  
 console.log(currDate)
</script>

2.2. 那是怎么着程度的移植?

原汁原味移植。h5pal从SDLPAL里活动(正是抄啦)了大批量的代码。SDLPAL是贰个依照SDL的跨平台版仙剑,它早就会顺风的运行在Windows、Linux、OS X、Symbian、PSP、Android等很各种阳台方面。

h5pal与SDLPAL有着同样的视角,就是落实仙剑的主程序,你只必要有仙剑的财富文件就足以运转总体游戏。

UA约定

掺杂开荒容器中,须要有一个UA标志位来剖断当前系统。

此处Android和iOS原生容器统一在webview中增加如下UA标识(相当于说,借使容器UA中有那几个标识位,就象征是quick意况-那也是os剖断的贯彻原理)

String ua = webview.getSettings().getUserAgentString();

ua += " QuickHybridJs/" + getVersion();

// 设置浏览器UA,JS端通过UA判断是否属于quick环境
webview.getSettings().setUserAgentString(ua);

// 获取默认UA
NSString *defaultUA = [[UIWebView new] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

NSString *version = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleShortVersionString"];

NSString *customerUA = [defaultUA stringByAppendingString:[NSString stringWithFormat:@" QuickHybridJs/%@", version]];

[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":customerUA}];

如上述代码中分头在Android和iOS容器的UA中增添重心的标记位。

UA约定

错落开荒容器中,须求有叁个UA标记位来判别当前系统。

此处Android和iOS原生容器统一在webview中增进如下UA标志(也便是说,假诺容器UA中有其一标志位,就代表是quick意况-那也是os剖断的落到实处原理)

String ua = webview.getSettings().getUserAgentString();

ua += " QuickHybridJs/" + getVersion();

// 设置浏览器UA,JS端通过UA判断是否属于quick环境
webview.getSettings().setUserAgentString(ua);

// 获取默认UA
NSString *defaultUA = [[UIWebView new] stringByEvaluatingJavaScriptFromString:@"navigator.userAgent"];

NSString *version = [[NSBundle mainBundle].infoDictionary objectForKey:@"CFBundleShortVersionString"];

NSString *customerUA = [defaultUA stringByAppendingString:[NSString stringWithFormat:@" QuickHybridJs/%@", version]];

[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent":customerUA}];

如上述代码中分头在Android和iOS容器的UA中增加重心的标志位。

但另一种境况是,若是本身想在一个独立的 JS 文件里拔取Razor,那以上的措施可不行,因为MVC不会直接表达JS文件,独有放到 Razor view里技术够。可是在此作者向我们推荐四个第三方类库,就可令你直接在单独的 JS 文件里应用 Razor

要么也得以直接通过Nuget实行设置:

本文由必威发布于必威-前端,转载请注明出处:如果我想在一个独立的 JS必威: 文件里使用,里

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。