決策 ADR-0008:TypeScript 模板為何只轉(zhuǎn)換 app 代碼為 JavaScript)
React Router 架構(gòu)決策 ADR-0008TypeScript 模板為何只轉(zhuǎn)換 app 代碼為 JavaScript【免費(fèi)下載鏈接】react-routerDeclarative routing for React項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/react-router本篇技術(shù)文章圍繞 React Router 倉庫中一份已采納的架構(gòu)決策記錄ADR展開Only support JS conversion for app code。該決策解釋了為什么在腳手架工具中為 JavaScript 用戶動態(tài)降級 TypeScript 模板時只轉(zhuǎn)換應(yīng)用目錄app/內(nèi)的代碼而不轉(zhuǎn)換構(gòu)建腳本與配置文件并剖析了 ESM/CJS 雙路線各自踩中的技術(shù)深坑。讀完本文你能理解模板即單一可信源的腳手架設(shè)計思想看懂 Node.js 模塊系統(tǒng)在真實(shí)工程中的約束并能對照 create-react-router 的當(dāng)前源碼驗(yàn)證這套決策最終是如何被模板化機(jī)制承接的。背景TypeScript 默認(rèn)、JavaScript 可選的選擇困境React Router及其前身 Remix的項(xiàng)目腳手架默認(rèn)使用 TypeScript但始終有一批用戶更傾向純 JavaScript。決策文檔日期 2023-01-20狀態(tài) accepted記錄了當(dāng)時npx create-remix的交互場景——CLI 會直接詢問用戶選擇 TS 還是 JS? npx create-remixlatest ? Where would you like to create your app? ./my-remix-app ? What type of app do you want to create? Just the basics ? Where do you want to deploy? Choose Remix App Server if youre unsure; its easy to change deployment targets. Remix App Server ? TypeScript or JavaScript? (Use arrow keys) ? TypeScript JavaScript這個語言選擇看似只是一個選項(xiàng)背后卻要維護(hù)兩套完整的項(xiàng)目模板由此引出了模板維護(hù)成本問題。模板方案的演進(jìn)從雙份模板到僅維護(hù) TS 變體文檔還原了一條清晰的演進(jìn)鏈最初為每個模板分別維護(hù) TypeScript 和 JavaScript 兩個變體。它能用但巨大的模板內(nèi)容被完整復(fù)制了兩份兩套變體極難維護(hù)——任何一處功能更新都要同步改兩次。改進(jìn)團(tuán)隊(duì)決定只維護(hù)每個模板的 TS 變體。當(dāng)用戶選擇 JavaScript 時CLI 會先把 TS 模板拷貝下來然后動態(tài)地把所有 TypeScript 相關(guān)代碼轉(zhuǎn)換成 JavaScript 等價物。這個方案的邊界劃分很關(guān)鍵轉(zhuǎn)換app/目錄即 Remix/React Router 應(yīng)用代碼內(nèi)的文件是可靠的因?yàn)檫@部分代碼由 Vite 統(tǒng)一構(gòu)建構(gòu)建管線對 TS/JS 的處理是透明的而轉(zhuǎn)換app/目錄之外的 TS 相關(guān)代碼則棘手且易錯。app 代碼內(nèi)可靠、app 代碼外易錯這個判斷是整個 ADR 的支點(diǎn)下面逐一拆解易錯具體難在哪里。為什么 app 目錄之外的轉(zhuǎn)換如此困難app/之外通常是什么package.json里的 scripts、server.ts/seed.ts這類 Node 直接執(zhí)行的腳本、vite.config.ts、tsconfig.json、種子數(shù)據(jù)工具等。這些文件繞過了構(gòu)建管線由 Node 直接加載于是 Node 的模塊解析規(guī)則成為硬約束。問題 1.ts文件的陳舊引用Stale references文檔給出的實(shí)例是 Indie 與 Blues 兩個官方棧模板當(dāng)用戶選擇JavaScript后模板里的構(gòu)建/啟動腳本仍然引用著server.ts和seed.ts腳本依賴也隨之引用了ts-node這類 TS 專用運(yùn)行工具——結(jié)果就是腳本直接跑不起來。這個問題揭示了一個通用規(guī)律模板轉(zhuǎn)換不僅是文件內(nèi)容翻譯還必須同時改寫所有指向這些文件的引用點(diǎn)package.jsonscripts、import 語句、工具鏈依賴。引用點(diǎn)分散在 JSON、shell 命令、代碼三種介質(zhì)中靜態(tài)改寫很容易漏。問題 2.aESM 路線.mjsapp/之外轉(zhuǎn) JS 時最直覺的做法是轉(zhuǎn)成 ESM 風(fēng)格的.mjs因?yàn)?Remix 應(yīng)用代碼本身就用 ESM 語法。但 ESM 在 Node 中有兩種啟用方式文檔逐一分析了兩者的死結(jié)方式 a在package.json中設(shè)置type: module—— 文檔指出這會立即破壞構(gòu)建因?yàn)樵撛O(shè)置作用于整個包目錄覆蓋到 app 代碼而不只是 app 之外的腳本與 Remix 的構(gòu)建配置產(chǎn)生沖突。方式 b使用.mjs擴(kuò)展名—— 看起來更有希望但.mjs文件的 import 說明符必須帶完整文件擴(kuò)展名。而原 TS 模板中的相對導(dǎo)入普遍不帶擴(kuò)展名于是出現(xiàn)這樣的困境// ./script.mjs (converted from ./script.js) import myHelper from ./my-helper; // Should this be converted to ./my-helper.mjs? // Probably, but can we be sure? myHelper();把無擴(kuò)展名相對導(dǎo)入可靠地補(bǔ)上正確擴(kuò)展名是不可治理untractable的——因?yàn)槟繕?biāo)文件未必都是.js/.mjs轉(zhuǎn)換器無法保證每一次補(bǔ)全都正確。文檔的結(jié)論是或許存在某種解法但復(fù)雜度代價過高。問題 2.bCJS 路線如果不用.mjsNode 會把 app 目錄外的腳本默認(rèn)當(dāng)作 CommonJS 處理。而 CJS 不支持 ESM 風(fēng)格的import/export那就需要把所有import/export改寫成require/module.exports。文檔補(bǔ)充了一條容易被忽視的約束轉(zhuǎn)換后的代碼是要給其他開發(fā)者閱讀和編輯的因此不能像構(gòu)建產(chǎn)物那樣生成一堆 import/export 的樣板適配代碼。import/export 的轉(zhuǎn)換或許可行但同樣復(fù)雜度代價很高。三條路線雙模板、ESM、CJS全部被排除后決策的收斂方向就清晰了。決策JS 轉(zhuǎn)換只覆蓋 app 代碼Only support JS conversion for app code, not for scripts or code outside of the Remix app directory.只為 app 代碼提供 JS 轉(zhuǎn)換不為 app 目錄之外的腳本和代碼提供轉(zhuǎn)換。這個決策的實(shí)質(zhì)是劃定轉(zhuǎn)換能力的可信邊界構(gòu)建管線管轄范圍內(nèi)app/的 TS→JS 轉(zhuǎn)換交給工具自動化構(gòu)建管線管轄范圍外Node 直接執(zhí)行的腳本與配置保持 TypeScript 原樣把語言選擇的責(zé)任上移給用戶——通過選擇模板來表達(dá)。用戶的三個選項(xiàng)與手動清理路徑根據(jù)決策用戶面對想用 JavaScript這一訴求時有三種選擇使用 TypeScript 模板使用 TypeScript 模板但app 目錄被自動轉(zhuǎn)換為 JSapp/外仍是 TS 文件與 TS 工具鏈;使用專門的 JavaScript 模板dedicated Javascript template。如果選項(xiàng) 2 殘留的 TS 讓用戶無法接受、又找不到合適的選項(xiàng) 3 模板文檔給出了完整的手動清理清單刪除tsconfig.json或替換為等價的jsconfig.json把 TS 專用工具替換為 JS 對應(yīng)物例如ts-node-node把剩余的.ts文件改為.mjs并同步更新所有引用點(diǎn)——包括 import 與package.jsonscripts 中的文件名引用。注意這份清單恰好對應(yīng)了前面分析的三類坑配置文件、工具鏈依賴、陳舊引用——手動操作時照單排查即可。源碼印證模板機(jī)制在 create-react-router 中的落地ADR 提出時 CLI 還內(nèi)置了TS 或 JS的交互式提問到了當(dāng)前倉庫的 create-react-router語言選擇已經(jīng)徹底模板化——在packages/create-react-router包內(nèi)檢索不到任何 TypeScript/JavaScript 的交互提問邏輯語言差異完全由--template指定的模板承載這正是 ADR 選項(xiàng) 3專門模板成為主流路徑后的自然演進(jìn)。當(dāng)前 CLI 的模板機(jī)制可以從源碼中完整驗(yàn)證模板來源的五種合法形式copyTemplate 的入口注釋明確列出——本地文件或目錄、GitHubowner/repo簡寫、owner/repo/directory簡寫、完整 GitHub 倉庫 URL、任意 tarball URL非法模板會拋出 CopyTemplateError。GitHub 簡寫解析copyTemplateFromGithubRepoShorthand 將owner/name[/path]拆段后經(jīng) codeload 下載倉庫 tarball 并解壓getRepoInfo 負(fù)責(zé)從tree分支 URL 中提取分支與子目錄。子目錄過濾tarball 解壓時通過 tar 的map鉤子copy-template.ts按前綴過濾只保留指定子目錄——這就是能選owner/repo/templates/basic這類倉庫內(nèi)某目錄作為模板的實(shí)現(xiàn)基礎(chǔ)。默認(rèn)模板copyTemplateToTempDirStep 中未傳--template時回退到 remix-run 官方 templates 倉庫的 default 模板printHelp 的--help輸出把上述五種模板形式與示例逐條列出私有倉庫還支持--token傳訪問令牌。CLI 流程index.ts 中整個創(chuàng)建流程是顯式步驟數(shù)組introStep→projectNameStep→copyTemplateToTempDirStep→copyTempDirToAppDirStep→ 依賴安裝與 git 初始化等模板拷貝先落到臨時目錄再復(fù)制到目標(biāo)目錄并在 copyTempDirToAppDirStep 中做文件沖突檢測--overwrite可強(qiáng)制覆蓋。這條源碼證據(jù)鏈說明ADR 時代的CLI 動態(tài)轉(zhuǎn)換與今天的模板選擇并不矛盾——--template機(jī)制把選語言變成了選模板把轉(zhuǎn)換的責(zé)任從 CLI 的脆弱字符串改寫前移到模板作者的一次性維護(hù)從工程上規(guī)避了問題 1、2.a、2.b 的全部風(fēng)險。要點(diǎn)回顧維護(hù) TS/JS 雙份模板的重復(fù)成本催生了僅維護(hù) TS 模板 動態(tài)轉(zhuǎn)換的中間方案app/之外的轉(zhuǎn)換在 ESM.mjs擴(kuò)展名強(qiáng)制、type: module污染全包與 CJSimport/export 全量改寫且產(chǎn)物需可讀兩條路上都代價過高加上package.jsonscripts 中的.ts陳舊引用問題最終決策為只轉(zhuǎn)換 app 代碼用戶因此擁有 TS 模板 / 半轉(zhuǎn)換模板 / 純 JS 模板三條路徑且文檔提供了tsconfig.json→jsconfig.json、ts-node→node、.ts→.mjs三步手動清理清單當(dāng)前 create-react-router 源碼顯示該決策的落地形態(tài)語言選擇交由--template模板機(jī)制承擔(dān)CLI 本身不再做任何交互式語言提問或代碼級 TS→JS 轉(zhuǎn)換。這份 ADR 的價值在于它示范了一種務(wù)實(shí)的架構(gòu)決策方法先窮舉候選路線并給出每條路線的失敗證據(jù)帶代碼示例再把能力邊界收縮到可靠區(qū)間內(nèi)剩下的復(fù)雜性交還給用戶可控的模板層。對于任何需要為多語言用戶提供腳手架的項(xiàng)目這都是一份可直接借鑒的決策樣本。【免費(fèi)下載鏈接】react-routerDeclarative routing for React項(xiàng)目地址: https://gitcode.com/GitHub_Trending/re/react-router創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考