<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Airbnb 是如何從 JavaScript 遷移到 TypeScript 的?

          共 8494字,需瀏覽 17分鐘

           ·

          2021-08-05 20:38

          作者 | Sergii Rudenko
          譯者 | 張健欣
          策劃 | 曉旭
          TypeScript 是 Airbnb 前端開發(fā)的官方語言。但是,采用 TypeScript 的過程和遷移一個包含成千上萬個 JavaScript 文件的成熟代碼庫不是一夕發(fā)生的。TypeScript 的采用經(jīng)過了最初提案、多數(shù)團(tuán)隊(duì)采用、測試階段,最后落地為 Airbnb 前端開發(fā)的官方語言。


          遷移策略


          大規(guī)模遷移是一項(xiàng)復(fù)雜的任務(wù),我們探討了從 JavaScript 遷移到 TypeScript 的幾種策略:
          1) 混合遷移策略。一份文件一份文件地逐步部分遷移,修復(fù)類型錯誤,不斷重復(fù)直到整個項(xiàng)目遷移完成。其 allowJS 配置選項(xiàng)允許我們在項(xiàng)目中同時擁有 TypeScript 和 JavaScript 文件,這使得這種方案變得可行!
          在混合遷移策略中,我們不必暫停開發(fā),可以一份文件一份文件地逐步遷移。不過,規(guī)模很大時,這可能花費(fèi)很長時間。另外,還需要對來自組織的不同部門的工程師進(jìn)行培訓(xùn)。
          2) 一次性全部遷移!將一個 JavaScript 項(xiàng)目或含有部分 TypeScript 的項(xiàng)目完全遷移到 TypeScript。我們需要增加一些 any 類型和 @ts-ignore 注釋,這樣項(xiàng)目編譯就不會報錯,但隨著時間的推移,我們可以用更具描述性的類型替換它們。
          選擇一次性全部遷移策略有幾個顯著的優(yōu)點(diǎn):
          • 跨項(xiàng)目的一致性:一次性全部遷移將保證每個文件的狀態(tài)相同,工程師不必記住他們可以在哪里使用 TypeScript 特性,以及編譯器在哪些地方會報錯。

          • 只修復(fù)一種類型比修復(fù)文件容易地多:修復(fù)整個文件可能非常復(fù)雜,因?yàn)槲募赡苡性S多依賴。使用混合遷移,更難追蹤遷移的實(shí)際進(jìn)度和文件的狀態(tài)。

          看起來,一次性全部遷移明顯更好!但是,對一個大而成熟的代碼庫執(zhí)行整體遷移的過程是一個重要且復(fù)雜的問題。為了解決這個問題,我們決定使用代碼修改腳本——codemods!通過我們最初手動遷移到 TypeScript 的過程,我們認(rèn)識到可以自動化的重復(fù)操作。我們?yōu)槊總€步驟制作了 codemods,并將它們組合到總體遷移管線中。
          根據(jù)我們的經(jīng)歷,并不能 100% 保證自動化遷移會產(chǎn)生一個完全沒有錯誤的項(xiàng)目,但是我們發(fā)現(xiàn)下面列出的步驟的組合為我們最終遷移到一個沒有錯誤的 TypeScript 項(xiàng)目提供了最好的結(jié)果。使用 codemods,我們能夠在一天內(nèi)將包含 50,000 行代碼和 1,000+ 文件的項(xiàng)目從 JavaScript 轉(zhuǎn)換為 TypeScript!
          基于這個管線,我們創(chuàng)建了一個稱為“ts-migrate”的工具:


          在 Airbnb,我們在前端代碼庫的很多重要部分使用了 React。這就是 codemods 的一些部分與基于 React 的概念相關(guān)的原因。ts-migrate 可以通過一些額外的配置和測試,與其它框架或庫一起使用。


          遷移過程的步驟


          讓我們了解一下將項(xiàng)目從 JavaScript 遷移到 TypeScript 所需的主要步驟,以及這些步驟是如何實(shí)現(xiàn)的:
          1) 每個 TypeScript 項(xiàng)目的第一步是創(chuàng)建一個 tsconfig.json 文件,如果需要,ts-migrate 可以生成這個文件。有一個默認(rèn)的配置文件模板和一個校驗(yàn)檢查,可以幫助我們確保所有項(xiàng)目的配置是一致的。
          下面是一個基本配置的示例:

          {
          "extends": "../typescript/tsconfig.base.json",
          "include": [".", "../typescript/types"]
          }
          2) 一旦 tsconfig.json 文件就位,下一步就是將源文件的文件后綴從.js/.jsx 改為.ts/.tsx 。將這一步自動化非常簡單,能夠避免大量人工工作。3)下一步是運(yùn)行 codemods!我們稱它們?yōu)椤安寮薄s-migrate 插件是可以通過 TypeScript 語言服務(wù)器訪問其他信息的 codemods。這些插件以字符串作為輸入,產(chǎn)生一個更新后的字符串作為輸出。可以使用 jscodeshift、TypeScript API、字符串替換或其它 AST 修改工具來進(jìn)行代碼轉(zhuǎn)換。
          在每一個步驟之后,我們會檢查 Git 歷史中是否有任何更改并提交它們。這有助于將遷移拉取請求拆分為更易于理解的提交,并跟蹤文件重命名。


          ts-migrate 包概覽


          我們將 ts-migrate 拆分為 3 個包:
          • ts-migrate

          • ts-migrate-server

          • ts-migrate-plugins

          這樣做,我們將轉(zhuǎn)換邏輯從核心運(yùn)行程序中分離出來,并為不同的目的創(chuàng)建多個配置。目前,我們有兩個主要配置:migration 和 reignore。
          雖然 migration 配置的目標(biāo)是從 JavaScript 遷移到 TypeScript,reignore 的目標(biāo)是通過忽略所有的錯誤來使得項(xiàng)目可以編譯。當(dāng)一個人有一個非常大的代碼庫并且正在執(zhí)行以下任務(wù)時,reignore 是非常有用的:
          • 升級 TypeScript 版本

          • 對代碼庫進(jìn)行重大更改或重構(gòu)

          • 改進(jìn)一些常用庫的類型

          這樣,即使存在一些我們不想立即處理的錯誤,我們也可以遷移項(xiàng)目。這使得 TypeScript 或庫的更新變得容易許多。
          這兩個配置都運(yùn)行在 ts-migrate-server 上,這個 ts-migrate-server 包括兩部分:
          • TSServer: 這部分與 VSCode 編輯器在編輯器與語言服務(wù)器之間進(jìn)行通信時所做的非常相似。TypeScript 語言服務(wù)器的一個新實(shí)例作為一個單獨(dú)的進(jìn)程運(yùn)行,開發(fā)工具使用語言協(xié)議與服務(wù)器通信。

          • Migration runner: 這部分運(yùn)行并協(xié)調(diào)遷移過程。它需要以下參數(shù):

          interface MigrateParams {
          rootDir: string; // path to the root directory
          config: MigrateConfig; // migration config, including list of
          // plugins it contains
          server: TSServer; // an instance of the TSServer fork
          }
          它執(zhí)行以下動作:
          1. 解析 tsconfig.json。

          2. 創(chuàng)建.ts 源文件。

          3. 將每個文件發(fā)送到 TypeScript 語言服務(wù)器進(jìn)行診斷。編譯器為我們提供了三種類型的診斷:語義診斷(semanticDiagnostics )、語法診斷(syntacticDiagnostics )和推理診斷(suggestionDiagnostics )。我們使用這些診斷來發(fā)現(xiàn)源代碼中有問題的地方。根據(jù)唯一的診斷編號和行號,我們可以確定潛在的問題類型并進(jìn)行必要的代碼修改。

          4. 在每個文件上運(yùn)行所有插件。如果文本由于插件的執(zhí)行而改變,我們就更新原始文件的內(nèi)容,并通知 TypeScript 語言服務(wù)器該文件已經(jīng)改變。

          你可以在 examples package 或 main package 中找到 ts-migrate-server 用法的示例。ts-migrate-example 還包括插件的基本示例。它們可分為 3 大類:
          • 基于 jscodeshift 的插件

          • 基于 TypeScript 抽象語法樹的插件

          • 基于文本的插件

          在代碼庫中有一組示例演示如何構(gòu)建各種插件,并將它們與 ts-migrate-server 結(jié)合使用。下面是一個轉(zhuǎn)換如下代碼的遷移管線的示例:
          function mult(first, second) {
          return first * second;
          }

          轉(zhuǎn)換為:
          function tlum(tsrif: number, dnoces: number): number {
          console.log(`args: ${arguments}`);
          return tsrif * dnoces;
          }
          ts-migrate 在上面的示例中做了 3 個轉(zhuǎn)換:
          • 反轉(zhuǎn)了所有標(biāo)識符 first -> tsrif

          • 向函數(shù)聲明添加了類型 function tlum(tsrif, dnoces) -> function tlum(tsrif: number, dnoces: number): number

          • 插入 console.log(‘a(chǎn)rgs:${arguments}’);


          通用插件


          實(shí)際的插件位于單獨(dú)的包中——ts-migrate-plugins。我們來看看其中一些插件。我們有兩個基于 jscodeshift 的插件:explicitAnyPlugin 和 declareMissingClassPropertiesPlugin。jscodeshift 是一個使用 recast 包將抽象語法樹(AST)轉(zhuǎn)換回字符串的工具。通過使用 toSource() 函數(shù),我們可以直接更新文件的源代碼。
          explicitAnyPlugin 背后的主要思想是從 TypeScript 語言服務(wù)器中提取所有語義診斷錯誤以及行號。然后,我們需要在診斷中指定的行上添加 any 類型。這種方法允許我們解決錯誤,因?yàn)樘砑?any 類型可以修復(fù)編譯錯誤。
          轉(zhuǎn)換前:
          const fn2 = function(p3, p4) {}
          const var1 = [];
          轉(zhuǎn)換后:
          const fn2 = function(p3: any, p4: any) {}
          const var1: any = [];
          declareMissingClassPropertiesPlugin 接受所有代碼為 2339(你能猜出這個代碼是什么意思嗎?)的診斷,如果它能找到缺失標(biāo)識符的類聲明,這個插件會使用 any 類型注解將它們添加到類主體中。從名字可以看出,這個 codemod 只適用于 ES6 類。
          下一類插件是基于 TypeScript AST 的插件。通過解析 AST,我們可以在源文件中生成具有如下類型的更新數(shù)組:
          type Insert = { kind: 'insert'; index: number; text: string };
          type Replace = { kind: 'replace'; index: number; length: number; text: string };
          type Delete = { kind: 'delete'; index: number; length: number };
          在生成更新后,剩下的唯一事情就是以相反的順序應(yīng)用這些更改。如果通過這些操作的結(jié)果,我們接收到新的文本,我們就更新源文件。讓我們來看看這些基于 AST 的插件:stripTSIgnorePlugin 和 hoistClassStaticsPlugin。
          stripTSIgnorePlugin 是遷移管線中的第一個插件。它從文件中刪除所有 @ts-ignore(@ts-ignore 注釋允許我們告訴編譯器忽略下一行中的錯誤)實(shí)例。如果我們正在將一個 JavaScript 項(xiàng)目轉(zhuǎn)換成 TypeScript,這個插件不會做任何事情。但是,如果這是一個有一部分 TypeScript 的項(xiàng)目(在 Airbnb,我們有一些處于這種狀態(tài)的項(xiàng)目),那么這是必不可少的第一步。只有在刪除 @ts-ignore 注釋后,TypeScript 編譯器才會發(fā)出所有需要解決的診斷錯誤。
          const str3 = foo
          ? // @ts-ignore
          // @ts-ignore comment
          bar
          : baz;
          轉(zhuǎn)換為:
          const str3 = foo
          ? bar
          : baz;
          在刪除 @ts-ignore 注釋后,我們運(yùn)行 hoistClassStaticsPlugin。這個插件遍歷文件中的所有類聲明。它決定我們是否可以提升標(biāo)識符或表達(dá)式,并確定是否已經(jīng)將賦值提升到類。
          為了能夠快速迭代并防止回歸,我們?yōu)槊總€插件和 ts-migrate 增加了一系列單元測試。


          React 相關(guān)插件


          reactPropsPlugin 將類型信息從 PropTypes 轉(zhuǎn)換為一個 TypeScript 屬性類型定義。這個插件是基于 Mohsen Azimi 編寫的非常棒的工具。我們只需要在包含至少一個 React 組件的.tsx 文件上運(yùn)行這個插件。reactPropsPlugin 查找所有 PropTypes 聲明,并嘗試用 AST 和簡單正則表達(dá)式(如 /number/)或更復(fù)雜的正則表達(dá)式(如 /objectOf$/)來解析它們。當(dāng)檢測到一個 React 組件(無論是函數(shù)式組件還是類組件),它將被轉(zhuǎn)換為一個具有新的 type Props = {…}; 屬性類型的組件。
          reactDefaultPropsPlugin 覆蓋了 React 組件的 defaultProps 模式。我們使用一種特殊類型來表示具有默認(rèn)值的 props:
          type Defined<T> = T extends undefined ? never : T;
          type WithDefaultProps<P, DP extends Partial<P>> = Omit<P, keyof DP> & {
          [K in Extract<keyof DP, keyof P>]:
          DP[K] extends Defined<P[K]>
          ? Defined<P[K]>
          : Defined<P[K]> | DP[K];
          };
          我們試圖找到默認(rèn)的 props 聲明,并將它們與上一步生成的組件 props 類型合并。
          狀態(tài)和生命周期的概念在 React 生態(tài)系統(tǒng)中很常見。我們在兩個插件中解決了它們。如果一個組件是有狀態(tài)的,reactClassStatePlugin 生成一個新的 type State = any; ,reactClassLifecycleMethodsPlugin 用適當(dāng)?shù)念愋妥⒔饨M件的生命周期方法。這些插件的功能可以擴(kuò)展,包括用更具描述性的類型替換 any 的能力。
          對狀態(tài)和 props 的類型支持有更多改進(jìn)的空間。然而,作為一個起點(diǎn),這個功能被證明是足夠的。我們還不涉及 hooks,因?yàn)橐婚_始遷移的時候,我們的代碼庫使用的是比較老的 React 版本。


          確保項(xiàng)目編譯成功


          我們的目標(biāo)是獲得一個可編譯的 TypeScript 項(xiàng)目,它的基本類型覆蓋不會導(dǎo)致應(yīng)用程序運(yùn)行時行為的改變。
          在進(jìn)行所有轉(zhuǎn)換和代碼修改之后,我們的代碼可能會有不一致的格式,并且一些 lint 檢查可能會失敗。我們的前端代碼庫依賴一個 prettier-eslint 設(shè)置——Prettier 用來自動格式化代碼,ESLint 確保代碼遵循最佳實(shí)踐。因此,我們可以通過從我們的插件運(yùn)行 eslint-prettier 來快速修復(fù)前面步驟可能引入的任何格式問題。
          遷移管線的最后一部分確保所有的 TypeScript 編譯沖突都得到解決。為了檢測和修復(fù)潛在的錯誤,tsIgnorePlugin 使用行號進(jìn)行語義診斷,并插入帶有有用解釋的 @ts-ignore 注釋,例如:
          // @ts-ignore ts-migrate(7053) FIXME: No index signature with a parameter of type 'string...
          const { field1, field2, field3 } = DATA[prop];
          // @ts-ignore ts-migrate(2532) FIXME: Object is possibly 'undefined'.
          const field2 = object.some_property;
          我們也增加了對 JSX 語法的支持:
          {*
          // @ts-ignore ts-migrate(2339) FIXME: Property 'NORMAL' does not exist on type 'typeof W... */}
          <Text weight={WEIGHT.NORMAL}>
          some text
          </Text>
          <input
          id="input"
          // @ts-ignore ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'string'.
          name={getName()}
          />
          在注釋中包含有意義的錯誤信息可以更容易地修復(fù)問題和重新訪問需要注意的代碼。這些注釋,結(jié)合 $TSFixMe (我們?yōu)?any 類型引入了自定義的別名 $TSFixMe 和函數(shù)類型——$TSFixMeFunction = (…args: any[]) => any; 。盡管最佳實(shí)踐是避免使用 any 類型,但使用它可以幫助我們簡化遷移過程,并明確哪些類型應(yīng)該重新訪問),使得我們可以收集有關(guān)代碼質(zhì)量的有用數(shù)據(jù),并確定可能存在問題的代碼區(qū)域。
          最后值得一提的是,我們需要運(yùn)行 eslint-fix 插件兩次。一次是在 tsIgnorePlugin 之前,給定的格式可能會影響我們在哪里得到編譯錯誤。另一次是在 tsIgnorePlugin 之后,因?yàn)椴迦?@ts-ignore 注釋可能會引入新的格式錯誤。
          總結(jié)


          我們的遷移故事正在進(jìn)行中:我們有一些遺留項(xiàng)目仍然在用 JavaScript,我們在代碼庫中仍然有大量的 $TSFixMe 和 @ts-ignore 注釋。
          但是,使用 ts-migrate 大大加快了我們遷移的過程和效率。工程師們能夠?qū)W⒂陬愋透倪M(jìn),而不是手動進(jìn)行逐文件的遷移。目前,我們的 600 萬行前端代碼庫的大約 86% 已經(jīng)轉(zhuǎn)換為 TypeScript,到今年年底,我們有望達(dá)到 95%。
          你可以檢出 ts-migrate 代碼,并在 GitHub 代碼庫的主包中找到如何安裝和運(yùn)行 ts-migrate 的說明。如果你發(fā)現(xiàn)了任何問題或者有任何改進(jìn)的想法,我們歡迎你的貢獻(xiàn)!
          Brie Bunge 是 Airbnb TypeScript 的幕后推動者,也是 ts-migrate 的創(chuàng)建者,對其致以最大的敬意。感謝 Joe Lencioni 幫助我們在 Airbnb 采用 TypeScript,并改進(jìn)我們的 TypeScript 基礎(chǔ)設(shè)施和工具。特別感謝 Elliot Sachs 和 John Haytko 對 ts-migrate 所做的貢獻(xiàn)。感謝所有一路提供反饋和幫助的人!


          后記


          我們在遷移過程中發(fā)現(xiàn)的一些有用的東西:
          TypeScript 的 3.7 版本引入了 @ts-nocheck 注釋,可以增加在 TypeScript 文件的頭部來禁用語義檢查。我們沒有使用這個注釋,因?yàn)樗安恢С?ts/.tsx 文件,但它也可以在遷移過程中成為一個很好的中間階段助手。
          TypeScript 的 3.9 版本引入了 @ts-expect-error 注釋。當(dāng)一行以 @ts-expect-error 注釋作為前綴時,TypeScript 將禁止報告該錯誤。如果沒有錯誤,TypeScript 會報告 @ts-expect-error 是不必要的。在 Airbnb 代碼庫,我們使用了 @ts-expect-error 而不是 @ts-ignore 。

          原文鏈接

          https://medium.com/airbnb-engineering/ts-migrate-a-tool-for-migrating-to-typescript-at-scale-cd23bfeb5cc

          逆鋒起筆是一個專注于程序員圈子的技術(shù)平臺,你可以收獲最新技術(shù)動態(tài)最新內(nèi)測資格BAT等大廠大佬的經(jīng)驗(yàn)增長自身學(xué)習(xí)資料職業(yè)路線賺錢思維,微信搜索逆鋒起筆關(guān)注!

          2021 年最佳 JavaScript 框架排名
          JavaScript 代理的驚人力量
          更快學(xué)習(xí) JavaScript 的 6 個思維技巧
          一行能裝逼的 JavaScript 代碼
          11 個 JavaScript 技巧

          瀏覽 45
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜人妖另类 | 欧美性爱男人天堂 | 操逼高清视频 | 影音先锋女人aV鲁色资源网站 | 超碰在线观看网站 |