:深入解析 @babel/plugin-transform-flow-strip-types)
Babel Flow 類型剝離實戰(zhàn)深入解析 babel/plugin-transform-flow-strip-types【免費下載鏈接】babel Babel is a compiler for writing next generation JavaScript.項目地址: https://gitcode.com/gh_mirrors/ba/babel導(dǎo)讀babel/plugin-transform-flow-strip-types是 Babel 生態(tài)中用于剝離stripFlow 類型注解的核心轉(zhuǎn)換插件其職責(zé)一句話即可概括Strip flow type annotations from your output code從輸出代碼中剝離 Flow 類型注解。本文以當(dāng)前倉庫中該插件的 README.md 為骨架結(jié)合其源碼實現(xiàn)與測試夾具展開深度講解。讀完本文你將掌握該插件的安裝與配置方法、requireDirective選項的精確語義、插件對各類 Flow 語法類型注解、import type、declare、類型轉(zhuǎn)換、this參數(shù)等的剝離規(guī)則以及它在babel/preset-flow中的角色定位。插件定位為何需要剝離類型Flow 是 Facebook 出品的 JavaScript 靜態(tài)類型檢查器它依賴以// flow開頭的類型注解。但瀏覽器與 Node.js 運行時并不認識這些注解直接執(zhí)行帶類型的代碼會拋出語法錯誤。因此在生產(chǎn)構(gòu)建時必須借助 Babel 這類編譯器將類型信息剝掉只留下純 JavaScript。在 Babel 的插件體系中這個工作由babel/plugin-transform-flow-strip-types完成。它屬于transform轉(zhuǎn)換類插件與只負責(zé)語法解析的babel/plugin-syntax-flow不同transform 插件在語法解析成功之后對 AST 進行實際修改刪除類型節(jié)點。從源碼結(jié)構(gòu)看該插件通過 index.ts 導(dǎo)出其核心注冊方式如下export interface Options { requireDirective?: boolean; } export default declare((api, opts: Options) { api.assertVersion(REQUIRED_VERSION(^7.0.0-0 || ^8.0.0)); // ... return { name: transform-flow-strip-types, inherits: syntaxFlow, // visitor 中定義了針對各 Flow 節(jié)點類型的剝離邏輯 }; });幾點值得注意的源碼事實插件通過api.assertVersion(^7.0.0-0 || ^8.0.0)聲明其兼容的 Babel 核心版本范圍通過inherits: syntaxFlow聲明繼承babel/plugin-syntax-flow這意味著使用該插件時無需再手動單獨添加 syntax 插件其Options接口當(dāng)前只暴露一個可配置項requireDirective布爾值默認false。安裝與基礎(chǔ)配置根據(jù) README.md該插件使用 npm 或 yarn 安裝npm install --save-dev babel/plugin-transform-flow-strip-typesyarn add babel/plugin-transform-flow-strip-types --dev注意插件應(yīng)當(dāng)作為devDependency安裝因為它是構(gòu)建期工具不會進入運行時依賴。從 package.json 可以看到它聲明了對babel/helper-plugin-utils與babel/plugin-syntax-flow的依賴并以babel/core^8.0.0為 peerDependency。在 Babel 配置如babel.config.json中啟用{ plugins: [babel/plugin-transform-flow-strip-types] }測試夾具中大量使用這種最簡形式例如 strip-types 夾具組的 options.json{ plugins: [transform-flow-strip-types] }如果項目中同時使用了其他 Flow 相關(guān)插件如babel/plugin-transform-flow-comments將類型轉(zhuǎn)為注釋而非刪除則需要通過all排除機制避免兩個插件同時生效該機制由 preset 配置層面處理詳見下文 preset 部分。配置項詳解requireDirective這是該插件唯一的可配置選項其語義為是否要求源碼中必須存在 Flow 指令directive注釋即// flow、// flow strict、// flow strict-local、// flow weak或// noflow。默認行為requireDirective: false默認情況下插件不檢查指令注釋只要源碼中出現(xiàn)了 Flow 語法就進行剝離。這適用于整個項目都是 Flow 代碼的常見場景。開啟后requireDirective: true開啟后行為分為兩種文件包含flow指令正常剝離類型同時移除指令注釋本身。文件不包含flow指令若文件沒有任何 Flow 語法則跳過剝離不報錯若文件包含 Flow 語法則拋出編譯錯誤A flow directive is required when using Flow annotations with therequireDirectiveoption.這一邏輯在 index.ts 源碼 中有完整實現(xiàn)。核心是正則const FLOW_DIRECTIVE /flow(?:\s(?:strict(?:-local)?|weak))?|noflow/;它在Program訪問器中掃描文件注釋若命中指令則從注釋文本中剔除指令字符串若注釋剩余部分只剩空白或*如/* flow */的殼則直接將整條注釋標(biāo)記為comment.ignore true不輸出若未命中且requireDirective為真則置skipStrip true跳過整個文件的剝離而一旦在該文件中又遇到 Flow 節(jié)點Flow訪問器會直接調(diào)用path.buildCodeFrameError拋出上述錯誤。測試夾具印證倉庫中 requireDirective 測試夾具組 用三個用例完整覆蓋了這三種分支夾具目錄輸入期望結(jié)果true-strips-with-directive// flow 帶類型注解的函數(shù)類型被剝離指令注釋被移除輸出function foo(numVal, strVal) {}true-does-not-throw-with-directive無flow、無任何 Flow 語法原樣輸出不拋錯true-throws-if-found-with-directive無flow但含 Flow 語法拋出指定錯誤信息options.json 中聲明throws例如true-strips-with-directive的 input.js// flow function foo(numVal: number, strVal: string) {}對應(yīng)的 output.jsfunction foo(numVal, strVal) {}注意// flow指令本身也從輸出中消失了——這正是源碼中comment.ignore true邏輯的效果。典型使用場景requireDirective: true適合漸進式引入 Flow的混合代碼庫只有標(biāo)記了flow的文件才做類型剝離其余文件保持原樣避免誤傷。剝離能力的全景各類 Flow 語法處理插件通過多個 visitor 實現(xiàn)對不同節(jié)點類型的處理。下面結(jié)合 strip-types 測試夾具 中的輸入樣例逐一說明。1. 函數(shù)與參數(shù)的類型注解strip-type-annotations/input.mjs 覆蓋了函數(shù)返回類型、參數(shù)類型、可選參數(shù)、泛型、this參數(shù)、對象方法 getter/setter 等幾十種形態(tài)function foo2(numVal: number) {} function foo6(nullableNum: ?number) {} function foo12(): number {} function foo17T() {} a1 functionT,S() {}; a2 { set fooProp(value: number) {} }; class Foo3 { get fooProp(): number {} }對應(yīng)的輸出會移除所有類型信息function foo2(numVal) {} function foo6(nullableNum) {} function foo12() {} function foo17() {} a1 function () {}; a2 { set fooProp(value) {} }; class Foo3 { get fooProp() {} }2. 變量、對象、元組與泛型類型測試還覆蓋了對象字面量類型、可選屬性、索引簽名、元組、聯(lián)合類型、typeof類型、rest 參數(shù)等例如var a3: { numVal: number; [indexer: string]: number }; var x1: number | string 4; var x3: typeof Y Y; function foo22(...rest: Arraynumber) {}3. 類型導(dǎo)入導(dǎo)出import type / export type源碼中的ImportDeclaration訪問器實現(xiàn)了一個精細邏輯index.ts遍歷specifiers統(tǒng)計importKind為type或typeof的個數(shù)若全部specifier 都是類型導(dǎo)入則整個import語句被刪除若只是部分則僅刪除類型相關(guān)的 specifier保留值導(dǎo)入。測試樣例strip-type-annotations/input.mjs 后半部分import type imp1 from bar; // 整條刪除 import type { imp2, imp3 } from baz; // 整條刪除 import {type T2, V1} from foo; // 只刪 T2保留 V1 import foo; // 無 specifier原樣保留 export type { foo1 }; // 刪除 export type * from foo; // 刪除4. declare 語句族declare系列declare class、declare module、declare function、declare var、declare export等是純類型聲明運行時無意義插件會直接移除。相關(guān)夾具strip-declare-statementsstrip-declare-modulestrip-declare-exportsstrip-declared-checks-annotation移除declare屬性5. 類型轉(zhuǎn)換、接口、別名與 opaque 類型TypeCastExpression(value: Type)類型轉(zhuǎn)換表達式visitor 會先沿著node.expression鏈一路穿透嵌套的類型轉(zhuǎn)換再用底層表達式替換整個節(jié)點index.tsstrip-interfaces-module-and-script刪除interface、module、script聲明strip-type-alias、strip-opaque-type-alias刪除type別名與opaque typestrip-typecasts、strip-tuples、strip-array-types對應(yīng)類型轉(zhuǎn)換、元組與數(shù)組類型。6. 類相關(guān)的特殊處理Class訪問器index.ts會將node.implements置為null移除implements Foo子句遍歷類體對于declare修飾的類屬性直接整條刪除其余類屬性清除variance與typeAnnotation但保留屬性本身。ClassPrivateProperty訪問器則專門處理#prop: Type私有字段的類型注解。測試中 class-fields-initialized 與 class-fields-uninitialized 分別驗證了已初始化與未初始化類字段的類型剝離property-variance-with-class-properties 則覆蓋了協(xié)變/逆變/-標(biāo)注的移除。7. this 參數(shù)與函數(shù)參數(shù)細節(jié)Function訪問器index.ts做了三件事若首個參數(shù)是名為this的Identifier直接params.shift()刪除Flow 中this參數(shù)不是真正參數(shù)遍歷所有參數(shù)清除AssignmentPattern左側(cè)與普通參數(shù)的optional?標(biāo)記對非方法節(jié)點將predicate如%checks置空。AssignmentPattern訪問器進一步清除默認參數(shù)左側(cè)的optional標(biāo)記。8. 泛型調(diào)用與 JSXCallExpression、OptionalCallExpression、NewExpression與JSXOpeningElement訪問器統(tǒng)一將node.typeArguments置為null用于剝離fooT()、new FooT()、FooT /等位置的泛型實參。strip-typeapp-call 夾具專門驗證了這一行為。指令注釋的變體strict / weak / noflow源碼中的指令正則同時匹配多種變體/flow(?:\s(?:strict(?:-local)?|weak))?|noflow/因此以下注釋都會被識別并在剝離類型后從輸出中移除// flow// flow strict// flow strict-local// flow weak// noflow測試夾具中strip-comment-strict 與strip-comment-strict-local驗證flow strict/flow strict-localstrip-comment-weak 輸入為// flow weakconst a 1;strip-comment-noflow 驗證noflowstrip-directive 驗證指令注釋整體被移除strip-comment-strict 等還覆蓋了/* flow */塊注釋形式——當(dāng)注釋中只剩指令而無其他文字時整個注釋殼被丟棄。在 babel/preset-flow 中的角色日常項目中通常不會直接引用本插件而是通過babel/preset-flow一鍵啟用。從 babel-preset-flow 的 package.json 可以看到preset 將babel/plugin-transform-flow-strip-types列為其直接依賴是 Flow 支持的核心組成。preset 的典型配置方式{ presets: [[babel/preset-flow, { requireDirective: true }]] }preset 會把requireDirective選項透傳給本插件。若你的項目還使用了 Flow 的保留為注釋方案babel/plugin-transform-flow-comments需要在這兩個插件之間做互斥選擇避免類型被重復(fù)處理。與 TypeScript 場景的區(qū)分倉庫中還存在 babel-plugin-transform-typescript 插件用于剝離 TypeScript 類型。兩者的定位完全不同本插件flow-strip-types針對 Flow 類型注解配套語法插件為babel/plugin-syntax-flowtransform-typescript針對 TypeScript 類型配套語法插件為babel/plugin-syntax-typescript。盡管兩者都做類型剝離但語法體系、AST 節(jié)點與選項集各不相同不可混用。選擇依據(jù)是項目源碼所采用的類型系統(tǒng)Flow 用本插件或babel/preset-flowTypeScript 用babel/preset-typescript。從源碼看整體剝離流程綜合 index.ts 的 visitor 結(jié)構(gòu)一次完整的類型剝離轉(zhuǎn)換大致經(jīng)歷以下階段Program 階段掃描文件所有注釋匹配 Flow 指令正則若requireDirective為真且未找到指令則置skipStrip true整個文件跳過剝離或?qū)Τ霈F(xiàn) Flow 語法處拋錯遍歷階段Flow訪問器作為兜底對所有 Flow 類型節(jié)點調(diào)用path.remove()直接刪除ImportDeclaration、Function、Class等訪問器負責(zé)對混合型節(jié)點做精細處理部分刪除、清空注解、保留值語義替換階段TypeCastExpression用底層表達式替換類型轉(zhuǎn)換節(jié)點CallExpression/NewExpression/JSX 訪問器清空typeArguments。每一步都有對應(yīng)的測試夾具驗證測試基礎(chǔ)設(shè)施由babel/helper-plugin-test-runner提供見 package.json 的 devDependencies夾具目錄input.*與output.*一一對應(yīng)options.json聲明插件參數(shù)或期望拋錯。小結(jié)babel/plugin-transform-flow-strip-types是 Babel 中把帶 Flow 類型的源碼轉(zhuǎn)換為純 JavaScript的關(guān)鍵一環(huán)。本文從安裝配置、requireDirective選項語義、各類 Flow 語法的剝離規(guī)則到其在babel/preset-flow中的角色完整梳理了它的工作機制。若想深入了解每種語法的具體輸出可直接查閱倉庫中的 test/fixtures 目錄input.*/output.*對即是最直觀的行為示例想研究實現(xiàn)細節(jié)則從 src/index.ts 開始閱讀 visitor 即可?!久赓M下載鏈接】babel Babel is a compiler for writing next generation JavaScript.項目地址: https://gitcode.com/gh_mirrors/ba/babel創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考