构建和发布你自己的JavaScript库:一份详尽指南
核心要点
本文由Adrian Sandu、Vildan Softic和Dan Prince同行评审。感谢所有SitePoint的同行评审者,使SitePoint的内容达到最佳状态!
我们经常使用库。库是打包好的代码,开发人员可以在他们的项目中使用它,这无疑节省了工作量,并避免了重复造轮子。拥有可重用的包(无论是开源的还是闭源的)都比重建相同的功能或从过去的项目中手动复制粘贴更好。
但除了打包的代码之外,库究竟是什么呢?除了一些例外情况外,库应该始终是一个文件,或者位于单个文件夹中的几个文件。它的代码应该单独维护,并在将其实现到项目中时保持不变。库应该允许你设置项目特定的配置和/或行为。把它想象成一个USB设备,它只允许通过USB端口进行通信。一些设备,如鼠标和键盘,允许通过设备提供的接口进行配置。
在本文中,我将解释库是如何构建的。尽管涵盖的大部分主题都适用于其他语言,但这篇文章主要关注构建JavaScript库。
首先,库使现有代码的重用非常方便。你无需挖掘旧项目并复制一些文件,只需引入库即可。这还会使你的应用程序碎片化,使应用程序代码库更小,更容易维护。
如果一个开源项目变得流行起来,并且越来越多的开发者使用它,那么人们很可能会加入并通过提交问题或为代码库做出贡献来帮助该项目。无论哪种方式,它都会使库及其依赖它的所有项目受益。
一个流行的开源项目也可能带来巨大的机会。一家公司可能会对你工作的质量印象深刻,并提供给你一份工作。也许一家公司会要求你帮助将你的项目集成到他们的应用程序中。毕竟,没有人比你更了解你的库。
对许多人来说,这仅仅是一种爱好——享受编写代码、帮助他人以及在此过程中学习和成长的过程。你可以突破你的极限,尝试新事物。
在编写第一行代码之前,应该清楚你的库的目的是什么——你必须设定目标。有了它们,你可以专注于你希望用你的库解决什么问题。记住,你的库应该比原始形式的问题更容易使用和记住。API越简单,用户学习使用你的库就越容易。引用Unix哲学:
只做一件事,并把它做好
问问你自己:你的库解决了什么问题?你打算如何解决它?你会自己编写所有内容,还是可以使用其他人的库?
无论库的大小如何,都尝试制定路线图。列出你想要的所有功能,然后尽可能多地删除功能,直到你拥有一个微小但功能齐全的库,就像最小可行产品一样。这将是你的第一个版本。从那里,你可以为每个新功能创建里程碑。本质上,你将项目分解成小块,使每个功能都更像一项成就,更令人愉快。相信我,这会让你保持理智。
我个人非常喜欢从最终用户的角度来处理我的库。你可以称之为以用户为中心的设计。从本质上讲,你正在创建库的概要,希望对其进行更多思考,并使其对任何选择使用它的人更方便。同时,你可以考虑哪些方面应该是可自定义的,这将在本文后面讨论。
最终的API质量测试是吃自己的狗粮,在自己的项目中使用你的库。尝试用你的库替换应用程序代码,看看它是否涵盖了你想要的所有功能。尝试使库尽可能精简,同时使其足够灵活,以便通过自定义(如本文后面所述)使其也适用于它们的极端情况。
以下是用户代理字符串库的实现或概要可能是什么样子:
<code>// 以空的UserAgent字符串开始 var userAgent = new UserAgent; // 创建并添加第一个产品:EvilCorpBrowser/1.2 (X11; Linux; en-us) var application = new UserAgent.Product('EvilCorpBrowser', '1.2'); application.setComment('X11', 'Linux', 'en-us'); userAgent.addProduct(application); // 创建并添加第二个产品:Blink/20420101 var engine = new UserAgent.Product('Blink', '20420101'); userAgent.addProduct(engine); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 userAgent.toString(); // 对引擎产品进行更多更改 engine.setComment('Hello World'); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 (Hello World) userAgent.toString(); </code>
根据库的复杂性,你可能还需要考虑结构。利用设计模式是构建库或克服某些技术问题的绝佳方法。它还降低了在添加新功能时重构大部分代码的风险。
使库变得强大的东西是灵活性,但也很难在你可以自定义的内容和不能自定义的内容之间划清界限。一个完美的例子是chart.js与D3.js。两者都是可视化数据的优秀库。Chart.js使创建和设置不同类型的内置图表变得非常容易。但是,如果你需要更多地控制图形,则需要使用D3.js。
有多种方法可以赋予用户控制权:配置、公开公共方法以及通过回调和事件。
库的配置通常在初始化期间完成,但一些库允许你在运行时修改选项。选项通常限于细枝末节,更改这些选项不应该做任何事情,除了更新这些值以供以后使用。
<code>// 在初始化时配置 var userAgent = new UserAgent({ commentSeparator: ';' }); // 使用公共方法进行运行时配置 userAgent.setOption('commentSeparator', '-'); // 使用公共属性进行运行时配置 userAgent.commentSeparator = '-'; </code>
可以公开方法来与实例交互,例如从实例检索数据(getter),将数据放入实例(setter)以及执行操作。
<code>var userAgent = new UserAgent; // 获取器,用于从所有产品中检索注释 userAgent.getComments(); // 用于打乱所有产品顺序的操作 userAgent.shuffleProducts(); </code>
回调有时会与公共方法一起传递,通常是在异步任务之后运行用户代码。
<code>var userAgent = new UserAgent; userAgent.doAsyncThing(function asyncThingDone() { // 异步操作完成后运行代码 }); </code>
事件具有很大的潜力。它们类似于回调,除了添加事件处理程序不应该触发操作。事件通常用于指示(你可能猜到了)事件!就像回调一样,你可以提供其他信息并返回库可以使用的值。
<code>var userAgent = new UserAgent; // 验证添加的产品 userAgent.on('product.add', function onProductAdd(e, product) { var shouldAddProduct = product.toString().length // 告诉库添加或不添加产品 return shouldAddProduct; }); </code>
在某些情况下,你可能希望允许用户扩展你的库。为此,你可以公开一个公共方法或属性供用户填充,就像Angular模块(angular.module('myModule'))和jQuery的fn(jQuery.fn.myPlugin)一样,或者什么也不做,只需让用户访问你的库的命名空间:
<code>// AngryUserAgent 模块 // 可以访问UserAgent命名空间 (function AngryUserAgent(UserAgent) { // 创建新的方法 .toAngryString() UserAgent.prototype.toAngryString = function() { return this.toString().toUpperCase(); }; })(UserAgent); // 应用程序代码 var userAgent = new UserAgent; // ... // EVILCORPBROWSER/1.2 (X11; LINUX; EN-US) BLINK/20420101 userAgent.toAngryString(); </code>
同样,这也允许你覆盖方法。
<code>// AngryUserAgent 模块 (function AngryUserAgent(UserAgent) { // 存储旧的 .toString() 方法以供以后使用 var _toString = UserAgent.prototype.toString; // 覆盖 .toString() UserAgent.prototype.toString = function() { return _toString.call(this).toUpperCase(); }; })(UserAgent); var userAgent = new UserAgent; // ... // EVILCORPBROWSER/1.2 (X11; LINUX; EN-US) BLINK/20420101 userAgent.toString(); </code>
在后一种情况下,允许用户访问你的库的命名空间,会减少你对扩展/插件定义方式的控制。为了确保扩展遵循某些约定,你可以(也应该)编写文档。
编写概要是测试驱动开发的良好开端。简而言之,这是在你编写实际库之前写下测试形式的标准。如果这些测试检查某个功能的行为是否符合预期,并且你在编写库之前就编写了这些测试,则该策略称为行为驱动开发。无论哪种方式,如果你的测试涵盖了库中的每个功能,并且你的代码通过了所有测试,你可以安全地假设你的库可以工作。
Jani Hartikainen解释了如何使用Mocha在“使用Mocha和Chai进行单元测试你的JavaScript”中编写单元测试。在“使用Jasmine、Travis和Karma测试JavaScript”中,Tim Evko展示了如何使用另一个名为Jasmine的框架设置一个很棒的测试管道。这两个测试框架非常流行,但还有许多其他类型的框架。
我前面在本文中创建的概要已经对预期输出进行了注释。这就是所有测试的开始:从期望开始。我的库的Jasmine测试如下所示:
<code>// 以空的UserAgent字符串开始 var userAgent = new UserAgent; // 创建并添加第一个产品:EvilCorpBrowser/1.2 (X11; Linux; en-us) var application = new UserAgent.Product('EvilCorpBrowser', '1.2'); application.setComment('X11', 'Linux', 'en-us'); userAgent.addProduct(application); // 创建并添加第二个产品:Blink/20420101 var engine = new UserAgent.Product('Blink', '20420101'); userAgent.addProduct(engine); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 userAgent.toString(); // 对引擎产品进行更多更改 engine.setComment('Hello World'); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 (Hello World) userAgent.toString(); </code>
一旦你完全对第一个版本的API设计感到满意,就该开始考虑架构以及你的库将如何使用。
你可能使用也可能不使用模块加载器。但是,选择实现你的库的开发人员可能会使用,因此你希望你的库与模块加载器兼容。但是哪个?你如何在CommonJS、RequireJS、AMD和其他模块加载器之间进行选择?
实际上,你无需选择!通用模块定义 (UMD) 是另一种旨在支持多个模块加载器的策略。你可以在网上找到不同版本的代码片段,你也可以在UMD GitHub存储库中找到不同的UMD版本,以使你的库与UMD兼容。使用其中一个模板开始你的库,或者使用你喜欢的构建工具添加UMD,你就不必担心模块加载器了。
如果你希望使用ES2015导入/导出语法,我强烈建议使用Babel编译成ES5,并结合Babel的UMD插件。这样,你就可以在项目中使用ES2015,同时仍然生成适合所有人的库。
我完全赞成对所有项目进行彻底的文档记录,但这通常被认为是很多工作,被推迟,最终被遗忘。
文档应该始终从基本信息开始,例如项目名称和描述。这将帮助其他人理解你的库的功能以及它是否适合他们。
你可以提供其他信息,例如范围和目标,以更好地告知用户,以及路线图,以便他们知道将来会发生什么或知道他们如何做出贡献。
当然,你需要让用户了解如何使用你的库。这从API文档开始。教程和示例是很好的补充,但编写这些可能需要大量工作。但是,内联文档并非如此。这些是可以解析并使用JSDoc转换为文档页面的注释。
一些用户可能希望更改你的库。在大多数情况下,这将是为了贡献,但有些人可能希望为私人使用创建自定义版本。对于这些用户,包含元任务的文档非常有用,例如构建库、运行测试、生成、转换或下载数据等的命令列表。
当你开源你的库时,贡献非常重要。为了指导贡献者,你可以在其中添加文档,解释做出贡献的步骤以及它应该满足的标准。这将使你更容易审查和接受贡献,并使他们更容易正确地进行贡献。
最后但并非最不重要的一点是,请包含许可证。从技术上讲,如果你选择不包含许可证,它仍然受版权保护,但并非每个人都知道这一点。
我发现ChooseALicense.com是一个很好的资源,可以让你在无需成为法律专家的情况下选择许可证。选择许可证后,只需将文本保存在项目根目录下的LICENSE.txt文件中即可。
版本控制对于一个好的库至关重要。如果你选择进行重大更改,用户可能希望继续使用对他们有效的版本。
当前事实上的版本命名标准是语义版本控制,或SemVer。SemVer版本由三个数字组成,每个数字表示不同的更改:主版本、次版本和修补程序版本。
如果你有git存储库,你可以向存储库添加版本号。你可以将它们视为存储库的快照。我们称之为标签。要创建标签,请打开终端并键入:
<code>// 以空的UserAgent字符串开始 var userAgent = new UserAgent; // 创建并添加第一个产品:EvilCorpBrowser/1.2 (X11; Linux; en-us) var application = new UserAgent.Product('EvilCorpBrowser', '1.2'); application.setComment('X11', 'Linux', 'en-us'); userAgent.addProduct(application); // 创建并添加第二个产品:Blink/20420101 var engine = new UserAgent.Product('Blink', '20420101'); userAgent.addProduct(engine); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 userAgent.toString(); // 对引擎产品进行更多更改 engine.setComment('Hello World'); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 (Hello World) userAgent.toString(); </code>
许多服务(如GitHub)将提供你所有版本的概述以及每个版本的下载链接。
许多编程语言都带有包管理器,或者可以使用第三方包管理器。这些允许我们专门为这些语言引入库。例如,PHP的Composer和Ruby的RubyGems。
Node.js(一种独立的JavaScript引擎)带有npm。如果你不熟悉npm,我们有一个很棒的初学者指南。
默认情况下,你的npm包将公开发布。别担心!你也可以发布私有包,设置私有注册表或完全避免发布。
要发布你的包,你的项目需要一个package.json文件。你可以手动执行此操作或使用交互式向导。要启动向导,请键入:
<code>// 在初始化时配置 var userAgent = new UserAgent({ commentSeparator: ';' }); // 使用公共方法进行运行时配置 userAgent.setOption('commentSeparator', '-'); // 使用公共属性进行运行时配置 userAgent.commentSeparator = '-'; </code>
version属性应与你的git标签匹配。此外,请确保拥有README.md文件。就像GitHub一样,npm也使用它作为呈现你的包的页面。
之后,你可以通过键入以下命令发布你的包:
<code>var userAgent = new UserAgent; // 获取器,用于从所有产品中检索注释 userAgent.getComments(); // 用于打乱所有产品顺序的操作 userAgent.shuffleProducts(); </code>
就是这样!你已经发布了你的npm包。
几年前,出现了另一个名为Bower的包管理器。但是,这个包管理器并非为特定语言设计,而是为特定平台——Web设计的。你可以在那里找到所有主要的前端资源。只有当你的库与浏览器兼容时,在Bower上发布你的包才有意义。
如果你不熟悉Bower,我们也有一个初学者指南。
与npm一样,你也可以设置私有存储库。你也可以在向导中完全阻止它发布。
有趣的是,在过去的一两年里,许多人似乎正在转向npm用于前端资源。尽管npm包主要是JavaScript,但许多前端包也在npm上发布。无论哪种方式,Bower仍然很流行,所以我绝对建议你也将你的包发布到Bower上。
我有没有提到Bower实际上是一个npm模块,并且最初是受其启发的?命令非常相似。要生成bower.json文件,请键入:
<code>// 以空的UserAgent字符串开始 var userAgent = new UserAgent; // 创建并添加第一个产品:EvilCorpBrowser/1.2 (X11; Linux; en-us) var application = new UserAgent.Product('EvilCorpBrowser', '1.2'); application.setComment('X11', 'Linux', 'en-us'); userAgent.addProduct(application); // 创建并添加第二个产品:Blink/20420101 var engine = new UserAgent.Product('Blink', '20420101'); userAgent.addProduct(engine); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 userAgent.toString(); // 对引擎产品进行更多更改 engine.setComment('Hello World'); // EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 (Hello World) userAgent.toString(); </code>
就像npm init一样,说明是自解释的。最后,要发布你的包:
<code>// 在初始化时配置 var userAgent = new UserAgent({ commentSeparator: ';' }); // 使用公共方法进行运行时配置 userAgent.setOption('commentSeparator', '-'); // 使用公共属性进行运行时配置 userAgent.commentSeparator = '-'; </code>
就这样,你已经将你的库发布到互联网上,供每个人在他们的Node项目和/或Web上使用!
核心产品是库。确保它解决了问题,易于使用且稳定,你将使你的团队或许多开发人员非常高兴。
我提到的许多任务很容易自动化,例如:运行测试、创建标签、更新package.json中的版本以及将你的包重新发布到npm和bower。这就是你进入持续集成领域并使用Travis CI或Jenkins等工具的地方。我前面提到的Tim Evko的文章也涉及到这一点。
你构建并发布过库吗?请在下面的评论部分分享!
创建你自己的JavaScript库有很多好处。首先,它允许你在多个项目中重用代码,从长远来看节省了时间和精力。其次,它可以帮助你以更结构化和可读的方式组织代码。这在处理大型项目或与其他开发人员合作时尤其有用。最后,创建你自己的库可以成为一个很好的学习体验,帮助你加深对JavaScript和软件开发原则的理解。
创建JavaScript库的第一步是定义其用途。你希望你的库提供什么功能?一旦你清楚地了解你希望你的库做什么,你就可以开始编写代码了。这通常涉及定义一系列提供所需功能的函数。然后,这些函数通过其他开发人员可以使用的公共API公开。
测试是开发JavaScript库的关键部分。有几种JavaScript测试框架可用,例如Jest、Mocha和Jasmine。这些框架允许你为你的函数编写单元测试,确保它们按预期工作。除了单元测试之外,你可能还需要编写集成测试来检查库的不同部分是否可以一起正常工作。
良好的文档对于任何软件库都是必不可少的。它帮助其他开发人员了解如何使用你的库以及每个函数的作用。你应该在库中包含每个函数的详细描述,包括其输入、输出和任何副作用。你还可以使用JSDoc等工具根据代码注释自动生成文档。
有多种方法可以分发JavaScript库。一种常见的方法是将其发布到npm等包管理器上。这允许其他开发人员使用简单的命令轻松安装你的库。你还可以通过将其托管在CDN(内容分发网络)上或在你的网站上提供下载链接来分发你的库。
维护JavaScript库包括修复错误、添加新功能以及使库与最新的JavaScript标准和实践保持同步。定期测试你的库并倾听用户的反馈非常重要。你还可以考虑对你的库进行版本控制,以便用户可以选择使用稳定版本或具有新功能的最新版本。
为了确保你的JavaScript库高效,你应该专注于编写简洁明了的代码。避免不必要的计算和内存分配。使用Chrome DevTools等工具来分析你的库并识别任何性能瓶颈。你还可以考虑压缩你的库以减小其文件大小并提高加载时间。
由于每个浏览器解释JavaScript的方式不同,因此确保浏览器兼容性可能是一个挑战。你可以使用Babel等工具将你的代码转换为与旧版浏览器兼容的JavaScript版本。你还应该在不同的浏览器中测试你的库,以识别和修复任何兼容性问题。
错误处理是开发JavaScript库的重要组成部分。你应该努力提供清晰、有帮助的错误消息,以帮助用户了解出了什么问题。你可以使用try/catch块来捕获和处理错误。你还可以考虑提供一种让用户报告错误和问题的方法。
有多种方法可以获得有关你的JavaScript库的反馈。你可以请其他开发人员审查你的代码,将你的库发布到论坛或社交媒体上,或者将其发布到npm等包管理器上并寻求反馈。你应该对批评持开放态度,并愿意根据你收到的反馈进行更改。
以上是设计和构建自己的JavaScript库:提示和技巧的详细内容。更多信息请关注PHP中文网其他相关文章!