給開源項目做貢獻,一方面能夠增加自己看源碼的積累升酣,另一方面也是對自身代碼能力的檢驗舷暮。因為開源項目本身已經(jīng)是完整的項目,對于開源項目我們能貢獻的大概分三種(docs文檔噩茄、bug脚牍、feature新特性),其中難易度為 docs < bug < feature巢墅,但我提倡入手的時候可以從修復(fù)bug開始,這樣更利于熟悉項目的代碼券膀。
以下以我最近給vue-i18n
的一次pr為例君纫,展示下從issue到pr的整體流程。
查看issue
https://github.com/kazupon/vue-i18n/issues/779芹彬,對于一個反映bug的issue蓄髓,我們首先需要確認下是否能夠復(fù)現(xiàn),該issue上面已經(jīng)提供了復(fù)現(xiàn)鏈接舒帮;demo中設(shè)置了formatFallbackMessages: true
会喝,在組件<i18n>
使用不在messages的I accept {tos}.
時陡叠,將其錯誤解析為I accept [object Object].
<i18n path="I accept {tos}." tag="div">
<template #tos>
<a href="about:blank">{{ $t('Terms of Service') }}</a>
</template>
</i18n>
整個issue中提到了formatFallbackMessages
這個配置,由于本人沒有使用過肢执,所以需要理解這個配置項枉阵,打開官網(wǎng)文檔發(fā)現(xiàn)該配置中文部分是缺失的(目前已經(jīng)補充)。fallback-interpolation文檔
注意message中的key是帶有占位變量的
const messages = {
ru: {
'Hello {name}': 'Здравствуйте {name}'
}
}
const i18n = new VueI18n({
locale: 'ru',
fallbackLocale: 'en',
formatFallbackMessages: true,
messages
})
當(dāng)模板template如下時:
<p>{{ $t('Hello {name}', { name: 'John' }}) }}</p>
<p>{{ $t('The weather today is {condition}!', { condition: 'sunny' }) }}</p>
將會輸出:
<p>Здравствуйте John</p>
<p>The weather today is sunny!</p>
該feature的作者原意是想以en
的翻譯文案作為message预茄,同時將en
的文案作為其他語言的key兴溜,這樣在代碼層面就可以很容易的理解多語言的內(nèi)容。理解了配置項的含義后耻陕,我們的修復(fù)bug之路就可以邁進下一步了拙徽。
查看源碼
首先我們查看項目的貢獻文檔(大多數(shù)項目都會有貢獻說明文檔),雖說文檔上讓開發(fā)者在個人項目的v8.x
分支編寫诗宣,但我通常都會在v8.x
切出fix分支膘怕,這樣更利于之后對該項目其他issue的貢獻。
我看代碼的流程通常是從調(diào)用方式開始看的召庞,但是這是在組件<i18n>
中使用岛心,所以在源碼中難以查找其調(diào)用方式,所以這次我們以配置項formatFallbackMessages
為入口查看裁眯。
通過全局搜索查到鹉梨,src/index.js的_warnDefault
方法中有其配置的判斷(且僅有這里有調(diào)用):
if (this._formatFallbackMessages) {
const parsedArgs = parseArgs(...values)
return this._render(key, 'string', parsedArgs.params, key)
} else {
return key
}
_warnDefault
是當(dāng)獲取不到相關(guān)key的時候進行調(diào)用,而在我們知道這個配置項的含義就是找不到key的使用使用翻譯值當(dāng)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
}
這段函數(shù)存皂,message會經(jīng)過formatter的interpolate方法,interpolate方法逢艘,以$t
的調(diào)用來簡單說明旦袋,會根據(jù)第二個參數(shù)的數(shù)據(jù)類型進行判斷并組合,例如$t('hello {1}', {1: 'me'})
會被編譯為hello me
它改、$t('hello {1}', ['me'])
也會被編譯為hello me
疤孕,當(dāng)然還有vnode的形式調(diào)用(v-html使用);經(jīng)過編譯后會根據(jù)interpolateMode
值進行不同的組合央拖,這里我們看到_warnDefault
中調(diào)用的_render
是傳遞寫死的string
祭阀,通過查看其他_render
的調(diào)用發(fā)現(xiàn)interpolateMode
還能是raw
。
測試
到這一步鲜戒,我們懷疑可能是interpolateMode
寫死string
的可能专控,為此我們可以寫一個test函數(shù),這段測試函數(shù)可以在原有的單元測試中添加遏餐,在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,設(shè)置formatFallbackMessages : true
失都,i8n的path設(shè)置為帶有參數(shù)的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
中的interpolateMode
為raw
柏蘑,輸出符合預(yù)想幸冻,確實是這個參數(shù)的原因,然后我們就可以進行修復(fù)工作了咳焚。
修復(fù)
通過調(diào)用的源頭發(fā)現(xiàn)洽损,interpolateMode
參數(shù)一直有傳遞進去,所以我們只要修改沿途調(diào)用的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
督惰,根據(jù)輸出test:unit測試是跑通的不傅,但是test:e2e報錯,提示我安裝jdk赏胚,當(dāng)時我想著代碼沒問題就可以提交pr了访娶。
提交pr
這里有個小技巧,在給element-ui
貢獻代碼時我就發(fā)現(xiàn)觉阅,如果你在commit信息中添加相關(guān)的issue編號崖疤,也就是https://github.com/kazupon/vue-i18n/issues/779
中最后的數(shù)字,那么在該issue中就會關(guān)聯(lián)到你提交的commit(盡管這時你還沒提交pr)典勇。
提交pr后等待機器自動跑通test(現(xiàn)在開源項目一般都會有這一步驟)劫哼,發(fā)現(xiàn)還是跑不通test:e2e,這時負責(zé)pr的老哥就過來指導(dǎo)我了:
慚愧慚愧割笙,其實開源項目說明我是這時候才認真看的权烧,根據(jù)要求修改后,我就嘗試在本地跑通test:e2e命令伤溉,安裝jdk后還是不能跑通般码,這令我很困惑畢竟單元測試也跑通了。
為此我嘗試了倆種方式確認:
1.回退到我修改之前的版本乱顾,跑test:e2e命令板祝,發(fā)現(xiàn)還是跑不通,那說明要不就我本地環(huán)境不一樣走净,或者本身就跑不通券时。
2.在項目的pr頁面查看我之前合并的pr,發(fā)現(xiàn)也是跑不通的伏伯,這時我就確定項目本身跑不通革为。
這樣子我就嘗試自己修復(fù)e2e測試,但無果舵鳞;第二天我發(fā)現(xiàn)主分支由作者本人更新了,拉下來后發(fā)現(xiàn)是能跑通e2e測試琢蛤,所以使用git pull vue-i18n v8.x --rebase
命令后(vue-i18n是我自己設(shè)置的遠程地址別名蜓堕,--rebase能讓我的commit延后到主分支之后)抛虏,再次提交pr就可以了。
后話
同一天套才,項目作者合并了我的pr迂猴,這段修復(fù)流程應(yīng)該就畫上了句號。但并不背伴,因為之前我們說過該配置沒有中文文檔沸毁,另外也有issue反映沒有中文文檔不好理解,所以我又提了一個pr用于docs文檔補充(https://github.com/kazupon/vue-i18n/pull/785
)傻寂。
其實給開源項目做貢獻息尺,能夠讓我在下班后學(xué)習(xí)新編碼結(jié)構(gòu)和對設(shè)計模式的理解,也能讓我暫時脫離對業(yè)務(wù)的編寫情緒中(當(dāng)然了疾掰,工作中有時候也會做優(yōu)化相關(guān)的有意思的工作)搂誉,所以我有空還是會上去貢獻過的項目中看看issue,能否作出pr貢獻静檬,這即是對開源項目的理解熟悉炭懊,也是對本身能力的提升。