談一次開源項目貢獻

給開源項目作貢獻,一方面可以增長本身看源碼的積累,另外一方面也是對自身代碼能力的檢驗。由於開源項目自己已是完整的項目,對於開源項目咱們能貢獻的大概分三種(docs文檔、bug、feature新特性),其中難易度爲 docs < bug < feature,但我提倡入手的時候能夠從修復bug開始,這樣更利於熟悉項目的代碼。html

如下以我最近給vue-i18n的一次pr爲例,展現下從issuepr的總體流程。vue

查看issue

github.com/kazupon/vue…,對於一個反映bug的issue,咱們首先須要確認下是否可以復現,該issue上面已經提供了復現連接;demo中設置了formatFallbackMessages: true,在組件<i18n>使用不在messagesI accept {tos}.時,將其錯誤解析爲I accept [object Object].node

<i18n path="I accept {tos}." tag="div">
  <template #tos>
    <a href="about:blank">{{ $t('Terms of Service') }}</a>
  </template>
</i18n>
複製代碼

整個issue中提到了formatFallbackMessages這個配置,因爲本人沒有使用過,因此須要理解這個配置項,打開官網文檔發現該配置中文部分是缺失的(目前已經補充)。fallback-interpolation文檔git

注意message中的key是帶有佔位變量的github

const messages = {
  ru: {
    'Hello {name}': 'Здравствуйте {name}'
  }
}

const i18n = new VueI18n({
  locale: 'ru',
  fallbackLocale: 'en',
  formatFallbackMessages: true,
  messages
})
複製代碼

當模板template以下時:npm

<p>{{ $t('Hello {name}', { name: 'John' }}) }}</p>
<p>{{ $t('The weather today is {condition}!', { condition: 'sunny' }) }}</p>
複製代碼

將會輸出:element-ui

<p>Здравствуйте John</p>
<p>The weather today is sunny!</p>
複製代碼

該feature的做者原意是想以en的翻譯文案做爲message,同時將en的文案做爲其餘語言的key,這樣在代碼層面就能夠很容易的理解多語言的內容。理解了配置項的含義後,咱們的修復bug之路就能夠邁進下一步了。設計模式

查看源碼

首先咱們查看項目的貢獻文檔(大多數項目都會有貢獻說明文檔),雖然說文檔上讓開發者在我的項目的v8.x分支編寫,但我一般都會在v8.x切出fix分支,這樣更利於以後對該項目其餘issue的貢獻。app

我看代碼的流程一般是從調用方式開始看的,可是這是在組件<i18n>中使用,因此在源碼中難以查找其調用方式,因此此次咱們以配置項formatFallbackMessages爲入口查看。dom

經過全局搜索查到,src/index.js_warnDefault方法中有其配置的判斷(且僅有這裏有調用):

if (this._formatFallbackMessages) {
  const parsedArgs = parseArgs(...values)
  return this._render(key, 'string', parsedArgs.params, key)
} else {
  return key
}
複製代碼

_warnDefault是當獲取不到相關key的時候進行調用,而在咱們知道這個配置項的含義就是找不到key的使用使用翻譯值當key,因此咱們接着往下看this._render方法:

_render (message: string, interpolateMode: string, values: any, path: string): any {
  let ret = this._formatter.interpolate(message, values, path)

  // If the custom formatter refuses to work - apply the default one
  if (!ret) {
    ret = defaultFormatter.interpolate(message, values, path)
  }

  // if interpolateMode is **not** 'string' ('row'),
  // return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter
  return interpolateMode === 'string' ? ret.join('') : ret
}
複製代碼

這段函數,message會通過formatter的interpolate方法,interpolate方法,以$t的調用來簡單說明,會根據第二個參數的數據類型進行判斷並組合,例如$t('hello {1}', {1: 'me'})會被編譯爲hello me$t('hello {1}', ['me'])也會被編譯爲hello me,固然還有vnode的形式調用(v-html使用);通過編譯後會根據interpolateMode值進行不一樣的組合,這裏咱們看到_warnDefault中調用的_render是傳遞寫死的string,經過查看其餘_render的調用發現interpolateMode還能是raw

測試

到這一步,咱們懷疑多是interpolateMode寫死string的可能,爲此咱們能夠寫一個test函數,這段測試函數能夠在原有的單元測試中添加,在test/interpolation.test.js中咱們找到了如何對<i18n>組件測試的方法:

describe('included translation locale message', () => {
  it('should be interpolated', done => {
    const el = document.createElement('div')
    const vm = new Vue({
      i18n,
      render (h) {
        return h('i18n', { props: { path: 'term' } }, [
          h('template', { slot: '0' }, [
            h('a', { domProps: { href: '/term', textContent: this.$t('tos') } })
          ])
        ])
      }
    }).$mount(el)
    nextTick(() => {
      assert.strictEqual(
        vm.$el.innerHTML,
        'I accept xxx <a href=\"/term\">Term of service</a>.'
      )
    }).then(done)
  })
})
複製代碼

咱們就依葫蘆畫瓢,增長一個describe,設置formatFallbackMessages : true,i8n的path設置爲帶有參數的string

describe('formatFallbackMessages', () => {
  let i18n
  beforeEach(() => {
    i18n = new VueI18n({
      locale: 'en',
      messages,
      formatFallbackMessages: true
    })
  })

  it('should be interpolated', done => {
    const el = document.createElement('div')
    const vm = new Vue({
      i18n,
      render (h) {
        return h('i18n', { props: { path: 'I am {0}' } }, [
          h('template', { slot: '0' }, [
            h('a', { domProps: { href: '/term', textContent: this.$t('tos') } })
          ])
        ])
      }
    }).$mount(el)

    nextTick(() => {
      assert.strictEqual(
        vm.$el.innerHTML,
        'I am <a href=\"/term\">Term of service</a>'
      )
    }).then(done)
  })
})
複製代碼

以後咱們經過修改_warnDefault中的interpolateModeraw,輸出符合預想,確實是這個參數的緣由,而後咱們就能夠進行修復工做了。

修復

經過調用的源頭髮現,interpolateMode參數一直有傳遞進去,因此咱們只要修改沿途調用的interpolateMode爲傳遞的值,最後傳遞進_render就能夠了。

if (this._formatFallbackMessages) {
  const parsedArgs = parseArgs(...values)
  return this._render(key, interpolateMode, parsedArgs.params, key)
} else {
  return key
}
複製代碼

進行補充測試item和全量測試

補充item後,命令行跑npm run test,根據輸出test:unit測試是跑通的,可是test:e2e報錯,提示我安裝jdk,當時我想着代碼沒問題就能夠提交pr了。

提交pr

這裏有個小技巧,在給element-ui貢獻代碼時我就發現,若是你在commit信息中添加相關的issue編號,也就是https://github.com/kazupon/vue-i18n/issues/779中最後的數字,那麼在該issue中就會關聯到你提交的commit(儘管這時你還沒提交pr)。

image.png

提交pr後等待機器自動跑通test(如今開源項目通常都會有這一步驟),發現仍是跑不通test:e2e,這時負責pr的老哥就過來指導我了:

image.png

慚愧慚愧,其實開源項目說明我是這時候才認真看的,根據要求修改後,我就嘗試在本地跑通test:e2e命令,安裝jdk後仍是不能跑通,這令我很困惑畢竟單元測試也跑通了。

爲此我嘗試了倆種方式確認: 1.回退到我修改以前的版本,跑test:e2e命令,發現仍是跑不通,那說明要不就我本地環境不同,或者自己就跑不通。 2.在項目的pr頁面查看我以前合併的pr,發現也是跑不通的,這時我就肯定項目自己跑不通。

這樣子我就嘗試本身修復e2e測試,但無果;次日我發現主分支由做者本人更新了,拉下來後發現是能跑通e2e測試,因此使用git pull vue-i18n v8.x --rebase命令後(vue-i18n是我本身設置的遠程地址別名,--rebase能讓個人commit延後到主分支以後),再次提交pr就能夠了。

後話

同一天,項目做者合併了個人pr,這段修復流程應該就畫上了句號。但並不,由於以前咱們說過該配置沒有中文文檔,另外也有issue反映沒有中文文檔很差理解,因此我又提了一個pr用於docs文檔補充(github.com/kazupon/vue… )。

其實給開源項目作貢獻,可以讓我在下班後學習新編碼結構和對設計模式的理解,也能讓我暫時脫離對業務的編寫情緒中(固然了,工做中有時候也會作優化相關的有意思的工做),因此我有空仍是會上去貢獻過的項目中看看issue,可否做出pr貢獻,這便是對開源項目的理解熟悉,也是對自己能力的提高。

相關文章
相關標籤/搜索