移植前准备
建议在适配js三方库前,使用[js-e2e]扫描三方库,检查是否存在node.js/web内置模块的依赖。
js-e2e工具时基于eslint进行封装,可分析出js库代码对node.js/web浏览器的内置模块、对象的依赖及兼容es标准版本,使用该工具,可以快速知道该库是否依赖node.js/web内置模块。
如果扫描结果不依赖node.js/web内置模块,那么,这个库将比较轻松地适配。如果大量依赖node.js/web内置组件,这时可能需要fork源库代码,进行侵入式修改,或者再找是否存在更适合的其他三方库。
js三方库扫描工具介绍
js-e2e是基于eslint进行封装、配置规则,用于分析js库代码对nodejs和web浏览器的内置模块、对象的依赖及兼容es标准版本的工具,支持检查指定源码目录和指定三方库的兼容性。
1 使用git工具同步js-e2e代码
2 安装npm依赖包
3 安装自定义的eslint输出报告formatter,包含csv、csvsimple、vscode、vscodesimple
4 执行检查命令
注意事项
模块规范
注意将要移植使用的三方js库的模块规范,amd、cmd、commonjs、es module等模块规范,看你需要的三方库所使用的规范是哪种。
commonjs规范主要是为了弥补javascript没有标准的缺陷,已达到像python、ruby和java那样具备开发大型应用的基础能力,而不是停留在开发浏览器端小脚本程序的阶段。使用commonjs规范进行开发,需要依赖node.js环境。(例如浏览器不兼容commonjs的根本原因,在于缺少四个node.js环境的变量:module、exports、require、global)
amd规范(异步模块定义)是 requirejs 在推广过程中对模块定义的规范化产出,不是javascript原生支持。使用amd规范进行开发时,需要引入requirejs这个第三方函数库,通过define()来定义模块,采用require()语句来加载模块。
cmd(通用模块定义)是 seajs 在推广过程中对模块定义的规范化产出。使用cmd规范进行开发时,需要引入seajs这个第三方函数库。在cmd规范中,一个模块就是一个文件,使用define()语句定义模块,使用seajs.use()加载模块。
三者的区别
commonjs规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。
amd是异步加载模块,核心是预加载,先对依赖的全部文件进行加载,加载完了再进行处理。
cmd也是异步加载模块,是按需加载。amd和cmd最大的区别是对依赖模块的执行时机不同,而不是加载时机不同,二者皆为异步加载模块。
amd是前置依赖,在定义模块的时候就要声明其依赖的模块。
cmd是就近依赖,只有在用到某个模块的时候再去require。
模块(关键字module)
“内部模块”现在称作“命名空间”,“外部模块”现在简称为“模块”。
内部外部为一个相对概念,在es6和nodejs中,引入了模块的概念,即一个文件就是一个模块;“内部模块”即当前文件内的模块,“外部模块”即当前文件外的其他模块。
例如我们在ts工程下新建一个ts文件声明一个变量a,在另一个文件同样声明一个变量a,这时候会出现错误信息:
原因:因为该文件内容是对全局可见的。
解决方案:只需要通过import || export引入模块系统即可。
es6:(ts也适用)
导出模块:export;(默认导出 export default)
导入模块:import;
commonjs和amd:
导出模块:module.exports 或 exports;
导入模块:require( );
为了支持commomjs和amd的exports,ts提供了export=语法:
arkts语言
harmonyos的arkts语言是一种基于typescript开发的语言,它专为harmonyos系统开发而设计。arkts语言结合了javascript的灵活性和typescript的严谨性,使得开发者能够快速、高效地开发出高质量的harmonyos应用程序。
arkts语言使用typescript语法,typescript是一种由微软开发的javascript超集语言,它支持javascript的所有语法,但添加了一些新的特性和语法,使开发更加可靠和高效。typescript最大的特点是引入了静态类型,开发者可以在编译时发现类型错误,提高代码的可维护性和可读性。
typescript基础知识包括基本类型、变量声明、函数、类、接口、泛型等。另外,typescript还支持模块化开发,可以使用es模块规范或者commonjs规范导入和导出模块。在实际项目开发中,统一指定采用一种模块规范,标准的es模块规范。虽然aekts使用了typescript的语法,但是也是有区别的,并不完全支持typescript的所有特性。
关于从typescript到arkts的适配规则,参见官方文档:
从typescript到arkts的适配规则typescript-to-arkts-migration-guide.md · openharmony/docs - gitee.com
注意在移植三方的js软件时,模块的导入导出是可能需要修改三方库源码的。
鸿蒙(harmonyos)的arkts(ark typescript)使用的模块规范是es6模块规范,而不是commonjs模块规范。
es6模块规范(也称为ecmascript 2015模块规范)是一种现代的模块系统,它使用import
和export
关键字来导入和导出模块成员。es6模块规范支持静态导入和导出,具有更好的树摇(tree shaking)和代码拆分(code splitting)特性,有助于优化应用程序的性能和大小。
相比之下,commonjs模块规范是一种较旧的模块系统,它使用require
和module.exports
来导入和导出模块成员。commonjs模块规范主要用于node.js环境,并且在一些旧的浏览器环境中也有支持。
移植范例
鸿蒙官方的三方库地址:openharmony三方库中心仓
移植很简单,成功移植了几个开源三方js库,jsbn和sm-crypto,并且编写了鸿蒙的arkui的单元测试, 测试已通过。注意老的一些js库,在ts中使用时,需要d.ts类型声明文件。这个文件可以自己编写,也可以从网址找有没有对应的参考。一些常用的js三方库几乎都有现成的d.ts类型定义文件。可以在这里找下:github - definitelytyped/definitelytyped: the repository for high quality typescript type definitions.
sm-crypto仓库地址:一缕阳光116/sm-crypto
jsbn仓库地址: 一缕阳光116/jsbn
sm-crypto移植
找到d.ts对应的声明文件并根据需要修改。比如依赖了yyz116/jsbn的大数库。
//index.d.ts
import jsbn from '@yyz116/jsbn';
export interface keypairhex {
privatekey: string;
publickey: string;
}
export interface keypairpoint extends keypairhex {
k: jsbn.biginteger;
x1: jsbn.biginteger;
}
/**
* cipher mode
* - `0`:c1c2c3
* - `1`:c1c3c2
*/
export type ciphermode = 0 | 1;
export namespace sm2 {
// todo type of parameter of jsbn.biginteger constructor
function generatekeypairhex(): keypairhex;
function doencrypt(msg: string | arraylike<number>, publickey: string, ciphermode?: ciphermode): string;
function dodecrypt(encryptdata: string, privatekey: string, ciphermode?: ciphermode, outputtype?: {
output?: "string" | "array";
}): string;
function dosignature(msg: string | number[], privatekey: string, options?: {
pointpool?: keypairpoint[] | undefined;
der?: boolean | undefined;
hash?: boolean | undefined;
publickey?: string | undefined;
userid?: string | undefined;
}): string;
function doverifysignature(msg: string | number[], signhex: string, publickey: string, options?: {
der?: boolean | undefined;
hash?: boolean | undefined;
userid?: string | undefined;
}): boolean;
function getpoint(): keypairpoint;
}
export function sm3(input: string | arraylike<number>, hmac?: {
key: hexstring | arraylike<number>;
mode?: "hmac";
}): string;
// sm4.encrypt() expects utf8 strings (such as "hello"), while sm4.decrypt() expects hex strings (such as "8d0a1f").
export type hexstring = string;
export type utf8string = string;
export interface sm4modebase {
padding?: "none" | "pkcs#5" | "pkcs#7";
mode?: "cbc";
iv?: number[] | hexstring;
}
export interface sm4mode_stringoutput extends sm4modebase {
output: "string";
}
export interface sm4mode_arrayoutput extends sm4modebase {
output: "array";
}
export namespace sm4 {
function encrypt(
inarray: number[] | utf8string,
key: number[] | hexstring,
mode?: sm4modebase | sm4mode_stringoutput,
): string;
function encrypt(inarray: number[] | utf8string, key: number[] | hexstring, mode: sm4mode_arrayoutput): number[];
function decrypt(
inarray: number[] | hexstring,
key: number[] | hexstring,
mode?: sm4modebase | sm4mode_stringoutput,
): string;
function decrypt(inarray: number[] | hexstring, key: number[] | hexstring, mode: sm4mode_arrayoutput): number[];
}
对应的index.js文件,修改如下,es6的模块引入方式:
//index.js
import * as sm2 from './sm2/index.js';
import {sm3} from './sm3/index.js';
import * as sm4 from './sm4/index.js';
export { sm2 as sm2 };
export { sm3 };
export { sm4 as sm4 };
注意引入方式的区别,那个sm3为什么单独的{sm3} 呢?其实它导出的函数模块,而其它的sm2和sm4只是命名空间。
sm3.js中的原有的commonjs模块规范,需要修改,如下:
//sm3.js
import {sm3 as sm3, hmac } from '../sm2/sm3'
......
/*
module.exports = function (input, options) {
input = typeof input === 'string' ? utf8toarray(input) : array.prototype.slice.call(input)
if (options) {
const mode = options.mode || 'hmac'
if (mode !== 'hmac') throw new error('invalid mode')
let key = options.key
if (!key) throw new error('invalid key')
key = typeof key === 'string' ? hextoarray(key) : array.prototype.slice.call(key)
return arraytohex(hmac(input, key))
}
return arraytohex(sm3(input))
}
* */
export function sm3 (input, options) {
input = typeof input === 'string' ? utf8toarray(input) : array.prototype.slice.call(input)
if (options) {
const mode = options.mode || 'hmac'
if (mode !== 'hmac') throw new error('invalid mode')
let key = options.key
if (!key) throw new error('invalid key')
key = typeof key === 'string' ? hextoarray(key) : array.prototype.slice.call(key)
return arraytohex(hmac(input, key))
}
return arraytohex(sm3(input))
}
jsbn使用举例:
import jsbn from '@yyz116/jsbn'
var biginteger = jsbn.biginteger;
var bi = new biginteger('91823918239182398123');
console.log(bi.bitlength()); // 67
sm-crypto使用举例:
import {sm2} from '@yyz116/sm-crypto'
const ciphermode = 1 // 1 - c1c3c2,0 - c1c2c3,默认为1
let encryptdata = sm2.doencrypt(msgstring, publickey, ciphermode) // 加密结果
let decryptdata = sm2.dodecrypt(encryptdata, privatekey, ciphermode) // 解密结果
encryptdata = sm2.doencrypt(msgarray, publickey, ciphermode) // 加密结果,输入数组
decryptdata = sm2.dodecrypt(encryptdata, privatekey, ciphermode, {output: 'array'}) // 解密结果,输出数组
// 纯签名 + 生成椭圆曲线点
let sigvaluehex = sm2.dosignature(msg, privatekey) // 签名
let verifyresult = sm2.doverifysignature(msg, sigvaluehex, publickey) // 验签结果
// 纯签名
let sigvaluehex2 = sm2.dosignature(msg, privatekey, {
pointpool: [sm2.getpoint(), sm2.getpoint(), sm2.getpoint(), sm2.getpoint()], // 传入事先已生成好的椭圆曲线点,可加快签名速度
}) // 签名
let verifyresult2 = sm2.doverifysignature(msg, sigvaluehex2, publickey) // 验签结果
// 纯签名 + 生成椭圆曲线点 + der编解码
let sigvaluehex3 = sm2.dosignature(msg, privatekey, {
der: true,
}) // 签名
let verifyresult3 = sm2.doverifysignature(msg, sigvaluehex3, publickey, {
der: true,
}) // 验签结果
// 纯签名 + 生成椭圆曲线点 + sm3杂凑
let sigvaluehex4 = sm2.dosignature(msg, privatekey, {
hash: true,
}) // 签名
let verifyresult4 = sm2.doverifysignature(msg, sigvaluehex4, publickey, {
hash: true,
}) // 验签结果
// 纯签名 + 生成椭圆曲线点 + sm3杂凑(不做公钥推导)
let sigvaluehex5 = sm2.dosignature(msg, privatekey, {
hash: true,
publickey, // 传入公钥的话,可以去掉sm3杂凑中推导公钥的过程,速度会比纯签名 + 生成椭圆曲线点 + sm3杂凑快
})
let verifyresult5 = sm2.doverifysignature(msg, sigvaluehex5, publickey, {
hash: true,
publickey,
})
其他资源
ts模块化规范与命名空间_ts命名空间不需要导入-csdn博客
【坚果派】js开源库适配openharmony系列——第一期实操-电子发烧友网
node之sm-crypto模块,浏览器和 node.js 环境中sm国密算法库-csdn博客
sm-crypto: javascript对sm2、sm3、sm4的支持。
github - juneandgreen/sm-crypto: 国密算法js版
ts模块化规范与命名空间_ts命名空间不需要导入-csdn博客
github - wechat-miniprogram/sm-crypto: miniprogram sm crypto library
鸿蒙harmonyos实战-arkts语言(基本语法) - 知乎
arkui_napi: development framework for extending the js native module | 原生模块扩展开发框架
codelabs: 分享知识与见解,一起探索harmonyos的独特魅力。 - gitee.com
发表评论