如何開發公司組件庫(下)

上一篇中介紹了在開發公司的組件庫以前須要的準備工做,主要包括:需求評審、調研、組件設計和提出 issue。 本篇主要介紹在具體的開發環節須要注意哪些事項。html

建立開發環境

組件開發的時候須要不斷測試當前已開發的功能,這裏建議單獨配置一個私人 demo 項目用於本地開發。建立組件對應的初始庫(詳見Angular 的建立庫),執行ng build [庫名稱]生成本地的組件庫。配置完成後,使用npm link連接剛纔打包好的組件庫和 demo 項目,而後在 demo 項目的angular.json中添加preserveSymlinks: true(以下),完成建立開發環境。這是爲了防止本地庫的引用依賴錯誤。node

{
    "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
    "version": 1,
    "newProjectRoot": "projects",
    "projects": {
        "my-project": {
            "root": "",
            "sourceRoot": "src",
            "projectType": "application",
            "architect": {
                "build": {
                    "builder": "@angular-devkit/build-angular:browser",
                    "options": {
                       "outputPath": "target",
                       "index": "src/index.html",
                       "main": "src/main.ts",
                       "tsConfig": "src/tsconfig.app.json",
                       "polyfills": "src/showcase/polyfills.ts",
                       "preserveSymlinks": true,
...
複製代碼

肯定組件的開發方式

組件開發分爲自行開發組件和基於第三方組件的二次封裝,這兩種在開發時略有不一樣。前者一般是從自身的業務抽離而來好比標註組件,後者一般會有比較成熟的開源方案可供使用好比表格、圖譜等。一般自行開發組件的成本會比較高,在業務相關性低,交互邏輯複雜,有比較成熟的開源方案的狀況下,最好基於第三方組件作二次封裝。react

中介者模式

二次封裝一般有兩種方式,能夠用設計模式來表示:裝飾者模式與中介者模式。git

裝飾者模式是指在不改變原類及不使用繼承的狀況下,動態的擴展一個對象的功能。一般來講就是用裝飾器來實現一些原第三方組件作不到的功能。這種模式並不經常使用,這裏很少介紹。github

咱們一般使用的是中介者模式。中介者模式是指用一箇中介對象來封裝一系列的對象交互,中介者使各對象不須要顯式地相互引用,從而使其耦合鬆散,並且能夠獨立地改變它們之間的交互。 好比下面的例子中,TableComponent充當了一箇中介者,將輸入的data屬性轉化爲第三方組件st能夠接受的數據類型,所以能夠不加修改的直接引用原來的組件。typescript

// table.component.ts
export class TableComponent implements OnChanges {
  @Input()
  data: Data[];
  _data: STData[];

  get _data(): STData[] {
    return this.data.map((item, index) => {
      if (this.selectedRows && this.selectedRows.includes(index)) {
        return {
          checked: true,
          ...item
        };
      }
      return item;
    });
  }
}
複製代碼
<st [data]="_data" ...></st>
複製代碼

是否應該使用組件繼承

基於第三方組件的開發除了上述兩種模式,其實還能夠用繼承來實現。Angular 的組件是使用@Component裝飾器來實現的,所以組件的繼承等同於 TypeScript 中帶有類裝飾器的類的繼承。如下是一個組件繼承的簡單例子。像DerivedComponent同樣,派生類若是擁有本身的@Component,其實至關於被兩個@Component裝飾器修飾過構造函數。根據 TypeScript 的裝飾器組合原則,最外層的@Component被調用後對構造函數的修改會覆蓋裏層的@Component,最終生效的只是派生類的@Component中傳入的參數。所以,若是須要改變@Component中的參數,則須要傳入組件所需的所有參數。特別是providers中組件級的依賴,也是須要傳入的,但第三方組件每每不會導出它的組件級依賴,就會給開發形成困難。而若是直接繼承基類,像DirectDerivedComponent,該組件會繼承@Component中全部的元參數和 BaseComponent 提供的類型與構造函數。可是由於不能自定義組件的元參數,致使這種使用方式受限不少。npm

在 TypeScript 裏,當多個裝飾器應用在一個聲明上時會進行以下步驟的操做:json

  1. 由上至下依次對裝飾器表達式求值。
  2. 求值的結果會被看成函數,由下至上依次調用。
// BaseComponent 基類組件
@Component({
  selector: 'app-base',
  templateUrl: './base.component.html',
  styleUrls: ['./base.component.less'],
  providers: [BaseService]
})
export class BaseComponent {
  construct(baseService: BaseService) {}
}

// DerivedComponent 派生類組件
@Component({
  selector: 'app-derived',
  templateUrl: './derived.component.html',
  styleUrls: ['./derived.component.less'],
  providers: [BaseService] // 裝飾器中的元參數會覆蓋基類組件,所以須要從新提供 providers
})
export class DerivedComponent extends BaseComponent {}

// DirectDerivedComponent 直接繼承的派生類組件
export class DirectDerivedComponent extends BaseComponent {}
複製代碼

以上分析說明,在 Angular 中使用繼承的方式開發組件是一件受限不少且複雜的事情。並且 Angular 官方也沒有提供關於組件繼承相關的文檔。只能從源碼分析猜想組件繼承的機制。所以,全部這些其實只是想說明一件事:儘可能避免使用組件繼承。若是你足夠細心,會發如今 Angular 在DI 實戰一節有此說明。(不只 Angular, React 官方也強調避免使用組件繼承。)固然也不是徹底不能使用,當組件遵循第三方組件的大多數交互邏輯只是在視圖層有所區別時,才建議考慮使用繼承的方式實現。segmentfault

想了解更多關於組件繼承,這篇文章可能會有幫助。設計模式

引入第三方組件

若是你肯定引入第三方組件,此時很容易忽略的一個問題就是第三方組件的 npm 包依賴問題。請選擇第三方組件一個合適版本引入,確保兼容公司組件庫的核心依賴庫。

另外值得說明的是,在開發庫的過程當中,請在組件庫根目錄的 package.json 中的 devDependencies 中安裝全部的同級依賴。而在目標庫的 package.json 中使用 peerDependencies 說明同級依賴。關於爲什麼這樣作的緣由能夠參考 Angular 文檔開發 Angualr 庫與 ng-packagr 的文檔dependencies

實現一個最小 demo

在一切準備就緒以後,先不要馬上投入組件具體功能的實現中。我建議第一步實現一個最小的 demo,實現最基本最重要的功能,確保咱們的思路是可行的,至關於 POC。好比我在開發 table 組件的時候,初版的設計是這樣的。table 列是經過 jsx 語法來實現自定義的,列的 API 設計也依賴於此。若是你以前用 React 的話會以爲這看起來是很是天然的設計。

@Component({
  selector: 'table-demo',
  template: ` <bx-table [data]="data" [columns]="columns" ></bx-table> `
})
export class TableDemo {
  // 表格行配置
  columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name'
    },
    {
      title: 'Action',
      key: 'action',
      template: (value, record, index) =>
        `<a>Action 一 {{ record.name }}</a><nz-divider nzType="vertical"></nz-divider><a>Delete</a>`
    }
  ];
}
複製代碼

然而 Angular 是不支持這種語法的,這是 React 的特性。致使我只能另尋他法。最終選擇仿照 delon 實現自定義模板,而後從新改寫 API。因此這個環節能夠防止咱們犯那些很嚴重的錯誤。每每咱們第一次設計的 API 有諸多不合理之處,這是一個很好的機會進行調整,同時成功的 demo 也讓咱們有信心繼續開發下去。

繼續開發,逐步實現 issue 中的功能

接下來就須要咱們慢慢逐步地實現 issue 中所列的功能了,在這期間可能會遇到不少意想不到的困難致使開發須要延期,也可能會須要調整不少次 API 以及增刪一些功能。咱們是組件的設計者也是開發者同時也是使用者,咱們應該對它有本身的想法,開發時不斷和你們溝通,能夠據理力爭也能夠虛懷若谷,對它負責到底。這會是一個比較漫長的過程。咱們會像西西弗斯同樣,不斷踩坑爬坑,踩坑爬坑,踩坑爬坑。由於造輪子會比用輪子複雜許多,可能常常會碰到咱們知識的邊界,會比較痛苦。可是當你最痛苦的時候,就是你進步最快的時候(也是頭髮掉的最快的時候)。

tsconfig.json 中的路徑映射

在開發 angular 庫的過程當中,必然要進行打包(ng build)。因爲 angular 默認的打包工具(@angular-devkit/build-ng-packagr)來自於ng-packagr,這裏把我在使用ng-packagr時遇到的一個比較常見的坑和你們分享一下。

咱們常常須要用別名指代某個位置的文件,好比須要用@component/*指代node_modules/component/dist/*。像這樣的路徑映射 typescript 是經過 tsconfig.json 的compilerOptionsbaseUrlpaths來支持的。在 Angular 項目中會使用 angular.json 中指定的 tsconfig 文件,而 VS Code 編輯器中會使用根目錄下的 tsconfig.json 文件。所以有時會出現編輯器不能正確解析路徑映射的狀況,可是 angular 是能夠正常構建的。(來源見這裏

在開發庫的時候,通常來講路徑映射應該在主目錄下的 tsconfig.json中配置,但有時候須要在庫級別進行路徑映射,也就是在 tsconfig.lib.json進行配置。這裏值得注意的是,paths指向的路徑必須是已編譯的文件所在路徑,並且除了目標自己,也須要對目標的子文件進行映射。例如同處於projects目錄下的lib-alib-blib-b引入了lib-a,此時假如咱們必需要在lib-b裏進行路徑映射,那麼lib-b/tsconfig.lib.json須要配置大體以下。(關於此問題的討論詳見這裏

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "lib-a": ["../../dist/lib-a"],
      "lib-a/*": ["../../dist/lib-a/*"]
    }
  }
}
複製代碼

開發終於結束了

經歷一段時間辛苦的開發以後,組件終於能夠投入使用了。在組件發佈以前,請檢查如下工做是否完成。

  • 對比 issue 中的設計,查看是否完成全部的功能以及 API 是否有變更。若是有和 issue 設計不一致的地方,向你們說明緣由。固然,最好是在開發的時候就積極溝通,和同事達成一致。
  • 添加組件的 README,進行 API 說明。若是有必要的話,儘可能補充使用範例。
  • 編輯.gitignore使其再也不忽略相應的編譯文件,提交編譯文件。

接下來只須要按照流程進行發佈便可。

  1. 發起 MR(若是在開發前已經發起了 WIP 狀態的 MR,如今須要移除 WIP 標誌)
  2. Code Review
  3. 合併入主分支
  4. 決定是否發佈新版本
    • 切出 release 分支,更新版本號
    • 使用 cheers 發佈 beta 版本(默認會打上 tag)
    • 使用 demo 庫測試 beta 版本,測試無誤後合併入 master 分支,移除 release 分支

工做流介紹

咱們的項目通常採用 gitflow 做爲 git 工做流。它擁有兩個長期分支 develop 與 master 和一些臨時分支 feature分支、release 分支、hotfix 分支等。平常的開發在臨時分支上進行,開發完畢後合併入 develop 分支,更新版本的時候經過 release 分支分別合併入 develop 分支和 master 分支。下圖是 gitflow 的工做流圖。(瞭解更多移步這裏)

gitflow

不過組件庫項目開發通常採用github flow。github flow 與 gitflow 不一樣的是移除了 develop 分支,只需維護一個長期分支 master(實際上它更相似於 github flow,見下圖)。所以組件功能的更新不須要通過合入 develop 分支與切換 release 分支等操做,直接合入主分支便可。等到變更積累到必定量,會在主分支切出 release 分支,修改版本號而後打上 tag 進行發佈。如此設計緣由在於 gitflow 的工做流使用 develop (或 release 分支)來進行測試環境的更新,master 來進行生產環境的更新,適合於業務開發。可是組件庫不依賴於測試環境,所以也就無需 develop 分支。常見的開源項目通常都採用 github flow 的形式管理工做流。

github flow

至此全部的工做就都結束了,停下來歇一歇,總結一下開發過程當中的收穫,和同事們分享你的成長吧!

相關連接: 如何開發公司組件庫(上)

相關文章
相關標籤/搜索