一、 前言

由于node以及绝大多数前端库都是用JavaScript(以下简称JS)语言实现,而Angular是用TypeScript(以下简称TS)实现,虽然TS是JS的超集,但是由于TS和JS对于数据类型检验处理的异同,使得JS库并不能直接在TS环境直接使用,必须要使用一定格式发布并且有类型声明文件,才能在TS环境使用。

二、 JS模块化规范

JS库通常有全局模式和模块模式,由于大型项目通常会用到很多各种插件,导致命名冲突的可能较大,因此现在绝大多数JS库都是用模块封装发布。

模块化规范有AMD、CMD、CommonJS和UMD几种。

1.    CommonJS

CommonJS是服务器端模块的规范,Node.js采用了这个规范。

根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了AMD、CMD解决方案。

2.    AMD和RequireJS

AMD是"Asynchronous Module Definition"的缩写,意思就是“异步模块定义”。

AMD设计出一个简洁的写模块API:

define(id?, dependencies?, factory);
 

第一个参数 id 为字符串类型,表示了模块标识,为可选参数。若不存在则模块标识应该默认定义为在加载器中被请求脚本的标识。如果存在,那么模块标识必须为顶层的或者一个绝对的标识。

第二个参数,dependencies,是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。

第三个参数,factory,是一个需要进行实例化的函数或者一个对象。

通过参数的排列组合,这个简单的API可以从容应对各种各样的应用场景,如下所述。

3.    CMD和SeaJS

CMD是SeaJS 在推广过程中对模块定义的规范化产出

对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

CMD推崇依赖就近,AMD推崇依赖前置。

虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。

AMD的API默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。

4.    UMD

UMD兼容了AMD和CommonJS规范。

AMD规范主要考虑浏览器环境的特点,文件中服务器,需要异步加载依赖模块;CommonJS规范主要考虑服务器环境,文件都在本地,可以同步加载依赖模块。为了让一个JS库只发布一次就兼容两种环境,人们又设计出另一个更通用的规范UMD(Universal Module Definition),希望借此解决兼容问题。

UMD模块是一个既能按模块使用(比如import)也能作为全局对象使用(当运行在不用模块加载器的环境)。很多流行的库(比如Momemt.js)都是用这种方式写的。

UMD模块会检查是否存在模块加载程序环境,代码通常是这样:

;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(global, exports) :
typeof define === 'function' && define.amd ? define([global, 'exports'], factory) :
(factory(global, global));
}(this, (function (global, exports) { 'use strict';
// 库内容..
function myFunction() {}
exports.exportedFunc = myFunction; // 导出库的常量或方法
})));
 

UMD先判断当前环境是否支持Node.js(module、module.exports)以及AMD(define、define.amd),存在则使用其一加载模块,否则将库作为全局变量(Node 的全局对象为global,浏览器的全局对象为window)的属性。

三、 类型声明

前言提过,JS库必须要提供类型声明文件,才能在TS环境使用。

1.    文件名

类型声明文件的主文件名与JS库文件的主文件名相同,后缀为“d.ts”。比如库的文件名为“index.js”,则类型声明文件名为“index.d.ts”

2.    不使用命名空间

如果不考虑模块输出内容的命名冲突,可以不使用命名空间,直接导出内容。

比如有如下库(index.js)内容:

;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(global, exports) :
typeof define === 'function' && define.amd ? define([global, 'exports'], factory) :
(factory(global, global));
}(this, (function (global, exports) { 'use strict';
const myObj = {};
function myFunc(){} exports.myObj = myObj;
exports.myFunc = myFunc;
})));
 

则类型声明文件(index.d.ts)可以这么写:

export const myObj: any;
export function myFunc(): void;

导入库使用时可以直接使用“myObj”对象和“myFunc”方法,比如在TS(Angular)环境:

import { myObj, myFunc } from "myLib";

console.log(myObj);
myFunc();
 
也可以用通配符“*”导入模块的所有导出内容:
import * as myLib from "myLib";

console.log(myLib.myObj);
myLib.myFunc();

3.    使用命名空间

在大型项目中,当引入很多库时,如果库没有使用命名空间封装,库相互间的导出内容是有命名冲突可能的。因此发布模块时用命名空间封装发布是一个比较好的主意。

对于同样的库文件,类型声明文件(index.d.ts)可以这么写:

export namespace myLib {
export const myObj: any;
export function myFunc(): void;
}

导入库使用时可以直接使用“myObj”对象和“myFunc”方法,比如在TS(Angular)环境:

import { myLib } from 'myLib';

console.log(myLib.myObj);
myLib.myFunc();
 
还可以使用“as”语句为库起个别名:
import { myLib as lib } from 'myLib';

console.log(lib.myObj);
lib.myFunc();

 

四、 发布

1.    配置

编辑“package.json”文件,其中“main”属性为库文件名,“types”属性为类型声明文件:

{
"name": "myLib",
"version": "1.0.0",
"main": "./index.js",
"types": "./index.d.ts"
}
 

其中,“types”属性在TS 2.0版本是用“typings”属性的,二者之新版都可以使用,含义相同,但新版本推荐用前者。当然,如果JS库文件名是“index.js”,类型声明文件名是“index.d.ts”,并且都在包的根目录,那么“types”属性不写也是可以的,不过推荐显式地写好,在必要时可以提高可读性。

如果库还依赖其他的库,则需要在“package.json”的“dependencies”属性中注明所有依赖及其类型声明文件。

2.    类型声明文件与库一起发布

配置好以后,就可以用以下命令进行发布了:

npm publish
 

由于类型声明文件已经配置在“package.json”,因此publish命令会将类型声明文件一同发布。安装这个包的时候,也会将类型声明文件一同安装。

3.    类型声明文件发布到@types

“@types”组织下的包是通过types-publisher工具从DefinitelyTyped自动发布的。要把类型声明文件作为一个“@types”包来发布,需要向https://github.com/DefinitelyTyped/DefinitelyTyped提交一个“pull”请求。在“贡献指南”页面中找到更多详细信息:http://definitelytyped.org/guides/contributing.html

用这种方式发布的类型文件,在项目开发时需要单独安装类型声明:

npm install --save-dev @types/myLib

安装成功后,可以在项目的package.json文件的“devDependencies”节点看到类似如下内容:

"devDependencies": {
"@types/myLib": "^1.0.0"
}

五、 使用

1.    Node.js环境

在Node.js或使用RequireJS,可以这么写:

const myLib = require("./myLib");
console.log(myLib);

2.    TS环境

在TS(Angular)项目中,可以导入全部API:

import * as myLib from 'myLib';
console.log(myLib);

也可以仅导入部分API:

import { API1, API2 as myApi } from 'myLib';
console.log(API1, myApi);

3.    浏览器环境

在普通的浏览器环境中,则可以这么写:

<script src="./myLib.js"></script>
<script>
console.log(myLib);
</script>

六、 参考

https://www.jianshu.com/p/bd4585b737d7

https://github.com/umdjs/umd

https://github.com/DefinitelyTyped/DefinitelyTyped

http://www.typescriptlang.org/docs/handbook/declaration-files/library-structures.html

http://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html

发布兼容TS的JS库到nexus和npmjs的更多相关文章

  1. qs.js库 使用方法

    1.qs.js库说明 qs是一个url参数转化(parse和stringify)的js库. https://www.npmjs.com/package/qs 2.使用(以vue文件做示例) (1)基本 ...

  2. underscore.js -2009年发布的js库

    2009 Underscore.js 0.1.0发布 Underscore一个JavaScript实用库,提供了一整套函数式编程的实用功能,但是没有扩展任何JavaScript内置对象.它是这个问题的 ...

  3. 学以致用:手把手教你撸一个工具库并打包发布,顺便解决JS浮点数计算精度问题

    本文讲解的是怎么实现一个工具库并打包发布到npm给大家使用.本文实现的工具是一个分数计算器,大家考虑如下情况: \[ \sqrt{(((\frac{1}{3}+3.5)*\frac{2}{9}-\fr ...

  4. 精读《12 个评估 JS 库你需要关心的事》

    1 引言 作者给出了从 12 个角度全面分析 JS 库的可用性,分别是: 特性. 稳定性. 性能. 包生态. 社区. 学习曲线. 文档. 工具. 发展历史. 团队. 兼容性. 趋势. 下面总结一下作者 ...

  5. 基于node的前端组件包发布至nexus和npmjs

    目录 目录... 3 1. 前言... 1 2. 配置... 1 2.1. 建立组件的导出模块... 1 2.2. 建立组件入口文件... 1 2.3. 配置“ng-package.json”文件.. ...

  6. 对js库的调研研究------引用

    1. 引言 从以下几个方面来阐述这个问题: 特性. 稳定性. 性能. 包生态. 社区. 学习曲线. 文档. 工具. 发展历史. 团队. 兼容性. 趋势. 2.概述 & 精读 特性 当你调研一个 ...

  7. typescript+webpack构建一个js库

    依赖说明 入口文件 tsconfig配置 webpack配置文件 webpack入口文件配置 webpack为typescript和less文件配置各自的loader webpack的output配置 ...

  8. 仿照jquery封装一个自己的js库(二)

    本篇为完结篇.主要讲述如何造出轮子的高级特性. 一. css方法的高级操作 先看本文第一部分所讲的dQuery css方法 //css方法 dQuery.prototype.css=function( ...

  9. 仿照jquery封装一个自己的js库(一)

    所谓造轮子的好处就是复习知识点,加深对原版jquery的理解. 本文系笔者学习jquery的笔记,记述一个名为"dQuery"的初级版和缩水版jquery库的实现.主要涉及知识点包 ...

随机推荐

  1. 快学Scala 第二十一课 (初始化trait的抽象字段)

    初始化trait的抽象字段: trait Logged { println("Logged constructor") def log(msg: String){ println( ...

  2. 利用JVM在线调试工具排查线上问题

    在生产上我们经常会碰到一些不好排查的问题,例如线程安全问题,用最简单的threaddump或者heapdump不好查到问题原因.为了排查这些问题,有时我们会临时加一些日志,比如在一些关键的函数里打印出 ...

  3. vue-hash-calendar,移动端日期时间选择插件

    按照惯例,先上效果图 vue-hash-calendar 基于 vue 2.X 开发的日历组件 支持手势滑动操作·1 原生 js 开发,没引入第三方库 上下滑动 切换 周/月 模式 [周模式中] 左右 ...

  4. selenium-webdriver中的显式等待与隐式等待

    在selenium-webdriver中等待的方式简单可以概括为三种: 1 导入time包,调用time.sleep()的方法传入时间,这种方式也叫强制等待,固定死等一个时间 2 隐式等待,直接调用i ...

  5. Web Storage和cookie的区别——每日一题20190629

    Web Storage? 使用HTML5可以在本地存储用户的浏览数据. 使用的主要目的是为了克服Cookie带来的一些限制,当数据需要被严格控制在客户端上时,无需持续的将数据发回服务器 主要目标: 1 ...

  6. Adobe PS常用快捷键

    ps使用快捷键 新建图层    Ctrl+Shift+N 取消选择区  Ctrl + D 新建标题    Ctrl + N 图片放大 Alt+鼠标滑动 图片位置拖动    空格 + 鼠标拖动 移动图层 ...

  7. [Vijos] 遭遇战

    背景 你知道吗,SQ Class的人都很喜欢打CS.(不知道CS是什么的人不用参加这次比赛). 描述 今天,他们在打一张叫DUSTII的地图,万恶的恐怖分子要炸掉藏在A区的SQC论坛服务器!我们SQC ...

  8. RocketMQ事务消息学习及刨坑过程

    一.背景 MQ组件是系统架构里必不可少的一门利器,设计层面可以降低系统耦合度,高并发场景又可以起到削峰填谷的作用,从单体应用到集群部署方案,再到现在的微服务架构,MQ凭借其优秀的性能和高可靠性,得到了 ...

  9. 算数运算符and数据类型转换

    一元(单目)运算符有且只有一个运算参数,二元(双目)运算符有且只有两个运算参数. 二元运算符:+(加).-(减).*(乘)./(求商).%(求余) 一元运算符:+(正),-(负),++(自增),--( ...

  10. ‎Cocos2d-x 学习笔记(14.2) EventListener _paused _isEnabled _isRegistered

    监听器3个bool类型成员变量. 监听器能设置是否能够接收事件. 能随时接收事件进行处理,此时把它看做工作状态,需要满足条件:     _paused = false;     _isEnabled ...