<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>

          【實(shí)戰(zhàn)】1379- 基于 Yarn 的 Monorepo 實(shí)踐

          共 6619字,需瀏覽 14分鐘

           ·

          2022-07-13 14:45

          幾年前工作中整過(guò)幾個(gè) SDK 倉(cāng)庫(kù),當(dāng)時(shí) SDK 庫(kù)邏輯還比較簡(jiǎn)單,工程設(shè)計(jì)也不復(fù)雜:

          • ESLint+Prettier+GitHook

          • Rollup 打包

          • npm 私有倉(cāng)庫(kù)搭建

          隨即發(fā)包復(fù)用就解決了。

          隨著時(shí)間的推移,SDK 庫(kù)為了兼容各個(gè)端、完善開(kāi)發(fā)體驗(yàn)實(shí)現(xiàn)各種配套的調(diào)試工具等等逐漸變得復(fù)雜,之前簡(jiǎn)單的工程能力要實(shí)現(xiàn)源碼插件化、分包發(fā)布、定制化構(gòu)建等等能力會(huì)比較痛苦:

          • 簡(jiǎn)單目錄隔離劃分模塊

          • 手動(dòng)多次更新目錄 package.json 版本來(lái)發(fā)包

          • 多個(gè)端代碼復(fù)用一個(gè) tsconfig.json,存在端限制(比如不能使用 DOM)的目錄并不能正確的校驗(yàn)。

          • ......

          然后通過(guò)搜索你就會(huì)了解到了 Babel、React 等源碼都采用了 Monorepo 的方式管理,Babel 還用了 Lerna 工具來(lái)做發(fā)包工具等等業(yè)內(nèi)的實(shí)踐,但當(dāng)時(shí)借助 Lerna 搭建的一個(gè)倉(cāng)庫(kù)實(shí)踐體驗(yàn)沒(méi)有想象中的好......

          最近我用 Yarn 包管理工具實(shí)踐了一次 Monorepo 的工程化搭建,此文意在將實(shí)踐過(guò)程分享出來(lái)并說(shuō)說(shuō)我對(duì) Monorepo 的一些看法,僅供參考。

          搭建過(guò)程

          本地新建一個(gè)倉(cāng)庫(kù)的過(guò)程略過(guò):

          - packages/
          - xxx/
          - package.json
          - README.md
          - package.json
          - .prettierrc
          - .eslintrc.js
          - .editorconfig
          - commitlint.config.js
          - README.md

          可以看到源碼是 packages 目錄下存放的一個(gè)個(gè)模塊:

          • 源碼使用統(tǒng)一的配置,如 eslint、prettier 配置等

          • 不同模塊間有一個(gè)良好的目錄隔離

          引入 Yarn

          首選參照 yarn 官網(wǎng)在全局安裝:

          npm i -g yarn

          并在倉(cāng)庫(kù)根目錄中引入指定版本的 yarn:

          yarn set version berry

          此時(shí)你會(huì)發(fā)現(xiàn)倉(cāng)庫(kù)中出現(xiàn)了以下文件:

          - .yarn/
          - releases/
          - yarn-berry.cjs # berry版本源碼
          - .yarnrc.yml # yarn配置

          Yarn 配置

          配置主要關(guān)心這些應(yīng)該就足夠用了:

          httpProxy:'http://127.0.0.1:8899'
          httpsProxy:'http://127.0.0.1:8899'

          npmAuthIdent:'${USER:-root}:${TOKEN:-123456}'
          npmPublishRegistry:'https://mirrors.cn/npm/'
          npmRegistryServer:'https://mirrors.cn/npm/'

          unsafeHttpWhitelist:
          -mirrors.cn

          yarnPath:.yarn/releases/yarn-berry.cjs
          • 可能因公司內(nèi)網(wǎng)限制,必須使用網(wǎng)絡(luò)代理

          • 公司搭建了 npm 鏡像服務(wù),修改下包發(fā)包地址及相應(yīng)鑒權(quán)賬號(hào)密碼。

          • 針對(duì)公司鏡像域名放開(kāi) https 限制

          • 禁用 PnP 模式(后面聊)

          然后在 package.json 中添加:

          {
          "workspaces": [
          "packages/*"
          ],
          }

          配置 IDE

          如果你開(kāi)啟了 PnP 模式(沒(méi)開(kāi)啟可以忽略這一步),那么還要參照文檔操作下,不然 IDE 語(yǔ)言功能可能運(yùn)行不正常:

          yarn dlx @yarnpkg/pnpify --sdk vscode

          引入插件

          參照 yarn 文檔引入必要插件:

          • Typescript 插件是用于改進(jìn)使用體驗(yàn)的,它會(huì)在你安裝包 A 的同時(shí)去嘗試幫你安裝其類型 @types/A,這里不多介紹。

          yarn plugin import typescript

          • Workspace-tools 是工作區(qū)插件,必備。

          yarn plugin import workspace-tools

          • Version 插件是實(shí)現(xiàn)發(fā)布流的(本文所展示實(shí)踐未使用,不作過(guò)多介紹)。

          yarn plugin import version

          這時(shí)候你可能會(huì)發(fā)現(xiàn).yarnrc.yml 多了以下配置:

          plugins:
          -path:.yarn/plugins/@yarnpkg/plugin-typescript.cjs
          spec:'@yarnpkg/plugin-typescript'
          -path:.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
          spec:'@yarnpkg/plugin-workspace-tools'
          -path:.yarn/plugins/@yarnpkg/plugin-version.cjs
          spec:'@yarnpkg/plugin-version'

          類型配置

          接下來(lái)你要明確你的包(源碼)會(huì)在哪些環(huán)境去運(yùn)行,假設(shè)我們要在網(wǎng)頁(yè)上和服務(wù)器上運(yùn)行,那么類型配置如下:

          //tsconfig.isomorph.json
          {
          "compilerOptions": {
          "target":"es6"/*Specify ECMAScript target version:'ES3'(default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019'or'ESNEXT'.*/,
          "module":"commonjs"/*Specify module code generation:'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or'ESNext'.*/,
          "allowJs":true/*Allowjavascriptfilestobecompiled.*/,
          "resolveJsonModule":true,
          "skipLibCheck":true,
          "declaration":true/*Generatescorresponding'.d.ts'file.*/,
          "declarationMap":true/*Generatesasourcemapforeachcorresponding'.d.ts'file.*/,
          "sourceMap":true/*Generatescorresponding'.map'file.*/,
          "composite":true/*Enableprojectcompilation*/,
          "tsBuildInfoFile":"./dist/tsconfig.tsbuildinfo"/*Specifyfiletostoreincrementalcompilationinformation*/,
          "strict":true/*Enableallstricttype-checkingoptions.*/,

          /*ModuleResolutionOptions*/
          "moduleResolution":"node"/*Specify module resolution strategy:'node'(Node.js)or'classic'(TypeScriptpre-1.6).*/,
          "baseUrl":"./"/*Basedirectorytoresolvenon-absolutemodulenames.*/,
          "paths": {
          "@*": ["packages/*"]
          } /*Aseriesofentrieswhichre-mapimportstolookuplocationsrelativetothe'baseUrl'.*/,
          "typeRoots": ["src"] /*Listoffolderstoincludetypedefinitionsfrom.*/,
          },
          "include": ["src"]
          }
          //tsconfig.node.json
          {
          "extends":"./tsconfig.isomorph",
          "compilerOptions": {
          "baseUrl":".",
          "typeRoots": ["src"],
          "types": ["node"]
          },
          "include": ["src"]
          }
          //tsconfig.web.json
          {
          "extends":"./tsconfig.isomorph",
          "compilerOptions": {
          "lib": ["dom", "dom.iterable", "esnext"],
          "jsx":"react-jsx"
          }
          }

          這里的主要目的是為了讓每個(gè)包內(nèi)源碼能得到正確的校驗(yàn),每個(gè)包內(nèi)的目錄結(jié)構(gòu)是:

          -dist/# 構(gòu)建產(chǎn)物
          -src/# 包源碼
          -tsconfig.json# 繼承../../tsconfig.xxx.json的殼配置(讓Vscode等IDE正常開(kāi)啟語(yǔ)言功能)
          -package.json# 有統(tǒng)一的scripts(dev, dist)

          包腳手架

          接下來(lái)你要想好你的包分哪幾種類型,比如一種分法是:

          • 入口模塊(一般只有 1 個(gè)),命令為 cli

          • 核心模塊,命名模式為 core-xxx

          • 插件模塊,命名模式為 plugin-xxx

          • 工具模塊,命名模式為 utils-xxx

          • 服務(wù)端模塊,命名模式為 server-xxx

          • 配套調(diào)試工具模塊,命名模塊為 devtool-xxx

          然后你為每一種類型編寫好腳手架模板,引入腳手架工具即可:

          //package.json
          {
          "scripts": {
          "addpkg":"yo xxx"
          }
          }

          yarn addpkg

          研發(fā)流配置

          這里也要跟 Lerna 一樣先要問(wèn)一個(gè)問(wèn)題,每個(gè)包的版本是獨(dú)立的呢還是統(tǒng)一的,說(shuō)真的為了省事,建議統(tǒng)一方便很多,目前看起來(lái)也沒(méi)造成什么問(wèn)題。

          借助工作區(qū)插件的能力,直接配置 scripts:

          //package.json
          {
          "scripts": {
          "ws:ver":"yarn workspaces foreach --exclude '+(server-*)' -pv exec npm version",
          "ws:pub":"yarn workspaces foreach --exclude '+(server-*)' -vt npm publish --tag alpha --tolerate-republish",
          "ws:prebuild":"yarn workspaces foreach -j 1000 -pvA run prebuild",
          "ws:dev":"yarn workspaces foreach -j 1000 -pvA run dev",
          "ws:dist":"yarn workspaces foreach -pvtA run dist",
          }
          }
          • 通過(guò) yarn ws:ver 可以統(tǒng)一更改包版本

          • 通過(guò) yarn ws:pub 可以統(tǒng)一發(fā)布包,并且把 server-* 類型包排除

          • 通過(guò) yarn ws:dev/dist 可以本地一鍵編譯所有包

          使用體驗(yàn)

          依賴管理

          Yarn 是個(gè)包管理器,最核心的實(shí)現(xiàn)就是依賴安裝,其特性建議細(xì)看文檔這里簡(jiǎn)單帶過(guò):

          • Offline Cache:簡(jiǎn)單地說(shuō)就是會(huì)將下載的包以 zip 緩存在.yarn/cache/ 里。

          • Plug’n Play:安裝后會(huì)生成.pnp.cjs,Hack Node.js 原生 require 方法達(dá)到直接加載.yarn/cache/ 中的 zip 包效果。

          • Zero-Install:將.yarn/cache/ 和.pnp.cjs 提交到 Git 倉(cāng)庫(kù)中并開(kāi)啟 PnP 模式后,協(xié)作者無(wú)需再安裝即可開(kāi)發(fā)。

          PnP 模式跑起來(lái)后確實(shí)很爽,但后來(lái)因?yàn)樗木窒扌晕疫€是關(guān)掉了這個(gè)特性:

          PnP 只 Hack 到 require 方法,沒(méi)有辦法很好地 Hack 各種 resolve,很大程度上依賴生態(tài)需要?jiǎng)e的庫(kù)引入 SDK 支持它,比如 storybook 這類工具源碼中很多 require.resolve 以及手動(dòng)拼接的在 node_modules / 下的文件路徑,這種實(shí)現(xiàn)就需要其本身去兼容 PnP。

          修改.yarnrc.yml 配置以采用原生的 node_modules 安裝結(jié)構(gòu):

          nodeLinker:node-modules

          簡(jiǎn)單地執(zhí)行后,就能得到一個(gè)相對(duì)完美的結(jié)構(gòu):

          yarn install

          -node_modules/# 公共依賴
          -packages/
          -xxx/
          -node_modules/# 與公共依賴版本沖突的特殊依賴

          但這個(gè)實(shí)現(xiàn)也相對(duì)的復(fù)雜,作為使用者我還沒(méi)深入的看源碼理解其一些抽風(fēng)行為,平時(shí)你可能需要用到以下技巧:

          yarn dedupe xxx

          yarn rebuild husky

          • 有時(shí)候你會(huì)有鎖定依賴的需要。

          //package.json
          {
          "resolutions": {
          "roaring":"1.0.6",
          "typeorm":"0.2.34",
          "async":"3.2.0"
          }
          }

          工作區(qū)指令

          workspace 插件給 yarn 提供了批量給工作區(qū)(包)執(zhí)行命令的方式:

          yarn workspaces foreach ......

          這個(gè)命令最強(qiáng)大的地方的是它可以拓?fù)涫降貓?zhí)行,只要加上 -topological 參數(shù)即可。

          但是它識(shí)別工作區(qū)命令執(zhí)行完成的方式比較弱,就是進(jìn)程退出,所以當(dāng)我執(zhí)行 yarn ws:dev 時(shí),tsc -w 的編譯掛起后使得拓?fù)鋱?zhí)行就是個(gè)雞肋了,而且控制臺(tái)輸出的也不好。

          Link

          用過(guò) npm link 的人都知道體驗(yàn)很不好,但 yarn link 的實(shí)現(xiàn)目前來(lái)看也很雞肋。

          yarn link 實(shí)際上是基于 resolutions 來(lái)實(shí)現(xiàn)的,但經(jīng)常因?yàn)橐溄拥膫}(cāng)庫(kù)子孫依賴版本沖突不成功,而且成功后也常常跑不起來(lái)。

          據(jù)我自身的經(jīng)驗(yàn)來(lái)說(shuō) link 功能實(shí)現(xiàn)其實(shí)挺復(fù)雜,往往不是一個(gè)簡(jiǎn)單創(chuàng)建一個(gè)軟鏈就可以的,要考慮:

          • 當(dāng)加載到軟鏈模塊執(zhí)行其 require 時(shí),require 加載常常會(huì)尋址到其自身的 node_modules。

          • 需要結(jié)合軟鏈倉(cāng)庫(kù)的依賴重新計(jì)算依賴樹。

          • ......

          從我這次的實(shí)踐角度來(lái)看,現(xiàn)實(shí)情況往往不要想那么多,直接創(chuàng)建的軟鏈就完事。

          • npm v7 軟鏈到全局調(diào)試 CLI 工具:

          npm link

          • npm v6 鏈接其他倉(cāng)庫(kù):

          npm link /path/to/local/dependency

          總結(jié)

          以上,就是我簡(jiǎn)單實(shí)踐 Yarn Monorepo 的一些經(jīng)驗(yàn)之談。

          之所以選擇 Yarn 是因?yàn)槲也惶春?pnpm 軟鏈原理的實(shí)現(xiàn)(詳見(jiàn)參考),除了就事論事地去對(duì)比不同的工具,其實(shí)我選擇 Yarn 依舊是看重了其源碼倉(cāng)庫(kù)的質(zhì)量和背后 Facebook、Google 等公司的實(shí)力。

          謝謝,大家多多交流。

          參考


          瀏覽 101
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  操逼无码 | 久久成年人精品視频 | 99热免费观看 | 性生交大片免费看A片苹果 | 欧美自拍视频 在线 |