設計和構建你自己的JavaScript代碼庫:提示與技巧
代碼庫:我們一直在使用它們。代碼庫是開發者把他們會在項目中使用到的代碼打包起來形成的,這總能節省時間和避免重復造輪子。擁有一個可重復使用的包,不管是開源的還是閉源的,總比重復構建一樣特性的包或者從過去的項目中手動復制粘貼要好。
來自作者的更多文章
- Stop Maiming Bodies: The Perils of Pixel Font-Size
- ES2016: Should the Future of JavaScript Be Developer-Driven?
除了被打包的代碼,還可以更精確的形容代碼庫嗎?除了少數的例外,代碼庫通常只是一個文件,或者是在同一個文件夾里的幾個文件。它的代碼應該可以單 獨保存和在你的項目中正常使用。庫文件允許你根據項目的不同來調整結構或者行為。想象一下只能通過USB接口進行通訊的USB設備。一些設備,例如鼠標和 鍵盤,可以通過設備提供的接口來進行配置。
在這篇文章,我會解釋如何構建庫文件。盡管大部分的方法可以應用到其他語言,但這篇文章主要講述的是構建JavaScript庫文件。
為什么構建你自己的Javascript庫?
首先和最重要的,庫文件可以讓現有的代碼方便的重復利用。你不需要挖出陳舊的項目來復制文件,只需要引入庫文件。這也可以讓你的應用組件化,讓應用的代碼庫更小更易維護。
Christ Church Library (source)
任何可以讓實現一個具體的功能更容易或者可以被重復利用的抽象的代碼,都可以被打包進去庫文件。jQuery是一個有趣的例子。盡管jQuery的API有大量的簡化的DOM API,在跨瀏覽器DOM操作比較困難的過去有著相當重要的意義。
如果一個開源項目變得流行并且讓許多開發者使用它,人們很有可能通過提出問題或者貢獻代碼來參加它的開發。不管怎樣,都對庫和依賴它的項目有所幫助。
一個流行的開源庫也會帶來很好的機會。公司可能會對你的工作質量表示認可并給你發offer。可能公司會要求你把你的項目整合進他們的應用。畢竟,沒人可以比你更了解你的項目。
當然它可能只是一個習慣——享受敲碼,幫助他人并在這個過程中學習和成長。你可以提升你的極限和嘗試新的東西。
范圍和目標
在寫下第一行代碼之前,你需要確定你的庫的功能——你需要設置一個目標。通過這個目標,你可以專注于你想利用這個庫來解決的問題。要牢記你的代碼庫的原本的形式在解決問題上要更容易使用和記憶。API越簡單,使用者學習你的代碼庫就更容易。引入一個Unix的設計哲學:
只做一件事情并把它做好
問下你自己:你的代碼庫解決了什么問題?你打算怎么取解決它?你將會一個人完成全部工作,還是引入別人的代碼庫?
不管代碼庫體積有多大,嘗試制作一個路線圖。列出你想要的每一個特性,并將它們盡可能的打散,知道你有一個小巧但是能解決問題的代碼庫,就像 minimum viable product。這會成為你的第一個版本。從這里開始,你可以在每一個新特性建立里程碑。從本質上來說,你把你的項目變成了比特級的代碼塊,讓每一個特性完成的更好和更有趣。相信我,這會讓你保持狀態良好。
API設計
在我看來,我想以使用者的角度來開發我的代碼庫。你可以稱呼它為以用戶為中心的設計。在本質上,你正在創造你的代碼庫的大綱,給予它更多的思考和讓選擇它的人使用起來更方便。在同時,你需要思考在什么地方需要可以定制,這會在這篇文章的后面討論。
終極的API測試是嘗試一下自己的技術,在你的項目使用你的代碼庫。試著用你的代碼庫替換之前的代碼,看它是否滿足了你想要的特性。試著讓你的代碼庫盡可能的直觀,讓它可以更靈活的在邊界條件下使用,還有定制化(在之后的文章會講述)。
這是一個關于用戶代理字符串的代碼庫的大綱的可能會有樣子:
// Start with empty UserAgent string
var userAgent = new UserAgent;
// Create and add first product: EvilCorpBrowser/1.2 (X11; Linux; en-us)
var application = new UserAgent.Product('EvilCorpBrowser', '1.2');
application.setComment('X11', 'Linux', 'en-us');
userAgent.addProduct(application);
// Create and add second product: Blink/20420101
var engine = new UserAgent.Product('Blink', '20420101');
userAgent.addProduct(engine);
// EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101
userAgent.toString();
// Make some more changes to engine product
engine.setComment('Hello World');
// EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101 (Hello World)
userAgent.toString();
根據你的代碼的復雜度,你可能會在組織結構上花費一些時間。利用設計模式是組織你的代碼庫的好辦法,甚至可以解決一些技術問題。這還能避免加入新特性帶來的大面積重構。
靈活性和定制性
靈活性是讓代碼庫變得強大的要素,但是確定可以定制與不能定制的界限是很困難的。 chart.js 和D3.js是很好的例子。這兩個代碼庫都是用來進行數據可視化的。Chart.js可以很簡單的創建不同形式的內置圖表。但是如果你想對圖像進行更多的掌控,D3.js才是你需要的。
有好幾種方法可以把控制權交給用戶:配置,暴露公共方法,通過回調和事件。
配置代碼庫通常在初始化之前完成。但是一些代碼庫允許尼在運行時對配置進行修改。配置通常被細小的部分限制,只有為了之后的使用而修改它們的數值才是被允許的。
// Configure at initialization
var userAgent = new UserAgent({
commentSeparator: ';'
});
// Run-time configuration using a public method
userAgent.setOption('commentSeparator', '-');
// Run-time configuration using a public property
userAgent.commentSeparator = '-';
方法通常是暴露給實例使用的,比如說從實例中獲取數據,或者設置實例的數據和執行操作。
var userAgent = new UserAgent;
// A getter to retrieve comments from all products
userAgent.getComments();
// An action to shuffle the order of all products
userAgent.shuffleProducts();
回調通常是在公共的方法中被傳遞的,通常在異步操作后執行用戶的代碼。
var userAgent = new UserAgent;
userAgent.doAsyncThing(function asyncThingDone() {
// Run code after async thing is done
});
事件有很多種可能。有點像回調,除了增加事件句柄是不應該觸發操作的。事件通常用于監聽,你可能會猜到,這可是事件!更像回調的是,你可以提供更多的信息和返回一個數值給代碼庫去進行操作。
var userAgent = new UserAgent;
// Validate a product on addition
userAgent.on('product.add', function onProductAdd(e, product) {
var shouldAddProduct = product.toString().length < 5;
// Tell the library to add the product or not
return shouldAddProduct;
});
在一些例子中,你可能允許用戶對你的代碼庫進行擴展。因此,你需要暴露一些公共方法或者屬性來讓用戶填充,像Angular的模塊 (angular.module('myModule')
)和Jquery的 fn
(jQuery.fn.myPlugin
)或者什么都不做,只是簡單的讓用戶獲取你的代碼庫的命名空間:
// AngryUserAgent module
// Has access to UserAgent namespace
(function AngryUserAgent(UserAgent) {
// Create new method .toAngryString()
UserAgent.prototype.toAngryString = function() {
return this.toString().toUpperCase();
};
})(UserAgent);
// Application code
var userAgent = new UserAgent;
// ...
// EVILCORPBROWSER/1.2 (X11; LINUX; EN-US) BLINK/20420101
userAgent.toAngryString();
類似的,這允許你重寫方法。
// AngryUserAgent module
(function AngryUserAgent(UserAgent) {
// Store old .toString() method for later use
var _toString = UserAgent.prototype.toString;
// Overwrite .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();
在后面的例子中,允許你的用戶獲取代碼庫的命名空間,讓你在對擴展和插件的定義方面上的控制變小了。為了讓插件遵循一些約定,你可以(或者是應該)寫下文檔。
測試
對測試驅動開發(test-driven development)來 說,寫下大綱是良好的開始。簡單來說,指的是在你寫實際的代碼庫之前,在你寫下測試準則的時候。如果測試檢查的是你的代碼特性是否跟期待的一樣,以及你在 寫代碼庫之前寫測試,這就是行為驅動開發。不管怎樣,如果你的測試覆蓋了你的代碼庫的每一個特性,而且你的代碼通過了所有的測試。你可以確定你的代碼是可 以正常工作的。
Jani Hartikainen講述了如何利用Mocha來進行單元測試 Unit Test Your JavaScript Using Mocha and Chai。在使用Jsmine,Travis,Karma測試JavaScript (Testing JavaScript with Jasmine, Travis, and Karma)這篇文章中,Tim Evko展示了怎么通過另一個叫做Jasmine的框架來設置良好的測試流程。這兩個測試框架都是非常流行的,但還有適應別的需求的其他框架。
我在這篇文章前面撰寫的大綱,已經講述了它期待怎樣的輸出。這是一切測試的開始:從期望出發。關于我的代碼庫的一個Jasmine測試像是這樣:
describe('Basic usage', function () {
it('should generate a single product', function () {
// Create a single product
var product = new UserAgent.Product('EvilCorpBrowser', '1.2');
product.setComment('X11', 'Linux', 'en-us');
expect(product.toString())
.toBe('EvilCorpBrowser/1.2 (X11; Linux; en-us)');
});
it('should combine several products', function () {
var userAgent = new UserAgent;
// Create and add first product
var application = new UserAgent.Product('EvilCorpBrowser', '1.2');
application.setComment('X11', 'Linux', 'en-us');
userAgent.addProduct(application);
// Create and add second product
var engine = new UserAgent.Product('Blink', '20420101');
userAgent.addProduct(engine);
expect(userAgent.toString())
.toBe('EvilCorpBrowser/1.2 (X11; Linux; en-us) Blink/20420101');
});
it('should update products correctly', function () {
var userAgent = new UserAgent;
// Create and add first product
var application = new UserAgent.Product('EvilCorpBrowser', '1.2');
application.setComment('X11', 'Linux', 'en-us');
userAgent.addProduct(application);
// Update first product
application.setComment('X11', 'Linux', 'nl-nl');
expect(userAgent.toString())
.toBe('EvilCorpBrowser/1.2 (X11; Linux; nl-nl)');
});
});
一旦你對你的API設計的第一個版本完全滿意,是時候開始思考結構和你的代碼庫應該如何被使用。
模塊加載器兼容性
你或許使用過模塊加載器。使用你的代碼庫的開發者有可能使用加載器,所以你會希望自己的代碼庫與模塊加載器是兼容的。但兼容哪一個呢?應該怎么從CommonJS,RequireJS,AMD和其他加載器中挑選呢?
實際上,你不需要挑選!通用模塊定義(UMD)是一個目標就是支持多種加載器的規則。你可以在網上找到不同風格的代碼段,或者從這里 UMD GitHub repository學習并讓它與你的代碼庫兼容。從其中的某個模板開始,或者使用你喜歡的構建工具add UMD with your favorite build tool,你就不必再擔心模塊加載器的問題了。
如果你希望用上ES2015的 import
/export
語法,我建議使用Babel和Babel’s UMD plugin來將代碼轉換成ES5。通過這個方法你可以在你的項目中使用ES2015,同時生成兼容性良好的代碼庫。
文檔
我完全贊成在每一個項目中使用文檔。但這通常牽涉到大量的工作,導致編寫文檔被推遲和在最后被遺忘。
基本信息
文檔的編寫應當從項目的名字和描述之類基本的信息開始。這會對別人明白你的代碼庫做了什么和是否對他們有用有所幫助。
你可以提供像是適用范圍和目標之類的信息來更好的給用戶提供信息,而提供路線圖可以讓他們明白在未來可能會有什么新變化以及他們可以提供怎樣的幫助。
API,教程和例子
當然,你需要確保用戶知道如何去使用你的代碼庫。這從API文檔開始。教程和例子是很好的補充,但編寫他們會是一個龐大的工作。然而,內聯文檔Inline documentation不會這樣。下面是一些利用JSDoc的,可以被解析和轉換成文檔頁的注釋
元任務
一些用戶想對你的代碼庫作出改進。在大多數情況中,會是貢獻代碼,但有一些會創建一個私人使用的定制版本。對于這些用戶,為類似構建代碼庫,運行測試,生成,轉換和下載數據之類的元任務提供文檔很有幫助的。
貢獻
當你對你的代碼庫進行開源,得到代碼貢獻是很有幫助的。為了引導貢獻者,你可以增加一些關于貢獻代碼的步驟和需要滿足的標準的文檔。這會對你審閱和接納貢獻的代碼和他們正確的貢獻代碼有所幫助。
授權許可
最后一點,使用授權許可。從技術上講,即時你沒有選擇任何一種技術許可,你的代碼庫依然是擁有版權的,但不是每一個人都知道這一點。
我發現 ChooseALicense.com這個網站可以讓你一個不需要成為法律專家也能挑選授權許可。在挑選授權許可之后,只需要在項目的根目錄下添加 LICENSE.txt
文件。
將它打包和發布到包管理器
對一個好的代碼庫來說,版本是很重要的。如果你想要加入重大的變化,用戶可能需要保留他們現在正在使用的版本。
Semantic Versioning是流行的版本命名標準,或者叫它SemVer。SemVer版本包括三個數字,每一個代表不同程度的改變:重大改變,微小的改變和補丁
在你的Git倉庫加入版本和發布
如果你有一個git倉庫,你可以在你的倉庫添加版本數字。你可以把它想象成你的倉庫的快照。我們也叫它標簽 Tags。可以通過開啟終端和輸入下面的文字來創造標簽:
# git tag -a [version] -m [version message]
git tag -a v1.2.0 -m "Awesome Library v1.2.0"
很多類似GitHub的服務會提供關于所有版本的概覽和提供它們的下載鏈接。
發布一個通用倉庫
npm
許多編程語言自帶有包管理器,或者是第三方包管理器。這可以允許我們下載關于這些語言的特定代碼庫。比如PHP的Composer 和Ruby的RubyGems
Node.js,一種獨立的JavaScript引擎,擁有 npm,如果你對npm不熟悉,我們有一個很好的教程 beginner’s guide。
默認情況下,你的npm包會發布為公共包。不要害怕,你也可以發布私有包 private packages, 設置一個私有的注冊private registry, 或者根本不發布avoid publishing.
為了發布你的包,你的項目需要有一個 package.json
文件。你可以手動或者交互問答的方式來創建。通過輸入下面的代碼來開始問答:
`npm init`
這個版本屬性需要跟你的git標簽吻合。另外,請確定有README.md
文件。像是GitHub,npm在你的包的展示頁使用它。
之后,你可以通過輸入下面的代碼來發布你的包:
`npm publish`
就是這樣!你已經成功發布了你的npm包。
Bower
幾年前,有另一個叫做Bower的包管理器。這個包管理器,實際上不是為了特定的語言準備的,而是為了互聯網準備的。你可以發現大部分是前端資源。在Bower發布你的包的關鍵一點是你的代碼庫是否跟它兼容。
如果你對Bower不熟悉,我們也有一個教程beginner’s guide 。
跟npm一樣,你也可以設置一個私有倉庫private repository。你可以通過問答的方式避免發布。
有趣的是,在最近的一兩年,很多人轉為使用npm管理前端資源。近段npm包主要是跟JavaScript相關,大部分的前端資源也發布在了npm上。不管怎樣,Bower仍然流行。我明確的推薦你在Bower上發布你的包。
我有提到Bower實際上是npm的一種模塊,并最初是得到它的啟發嗎?它們的命令是很相似的,通過輸入下面的代碼產生bower.json
文件:
`bower init`
跟npm init
類似,指令是很直白的,最后,發布你的包:
`bower register awesomelib https://github.com/you/awesomelib`
像是把你的代碼庫放到了野外,任何人可以在他們的Node項目或者網絡上使用它!
總結
核心的產品是庫文件。確定它解決了問題,容易和適合使用,你會使得你的團隊或者許多開發者變得高興。
我提到的很多任務都是自動化的,比如:運行測試,創建標簽,在package.json
升級版本或者在npm或者bower重發布你的包。 這是你像Travis CI 或 Jenkins一樣踏入持續集成和使用工具的開始。我之前提到的文章 article by Tim Evko 也講述到了這點。
你構建和發布代碼庫了嗎?請在下面的評論區分享!
來自:http://www.zcfy.cc/article/design-and-build-your-own-javascript-library-tips-amp-tricks-939.html