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

          pnpm 源碼結(jié)構(gòu)及調(diào)試指南

          共 6751字,需瀏覽 14分鐘

           ·

          2022-10-21 08:13

          前言

          Hi~又是一段時(shí)間沒(méi)有更新了。前幾天?stateof js 2021?的調(diào)查結(jié)果發(fā)布了,今年在里面增加了關(guān)于 monorepo tools 的調(diào)查報(bào)告(參考鏈接: https://2021.stateofjs.com/en-US/libraries/monorepo-tools )。

          其中 pnpm 一舉登頂 2021 年最受歡迎的 monorepo 工具鏈。同時(shí)在用戶使用廣度以及其他方面也取得了不錯(cuò)的成績(jī)。

          b0e2e31a09ff27e989087ceb1d5c2199.webp

          剛好前段時(shí)間調(diào)試過(guò)一陣子 pnpm 以及貢獻(xiàn)過(guò)一些代碼,因此筆者對(duì) pnpm 的結(jié)構(gòu)也算有些了解,這篇文章將在筆者的理解范圍之內(nèi)給大家做一個(gè)代碼結(jié)構(gòu)解析,如果有問(wèn)題可以直接指出來(lái),一起探討學(xué)習(xí)。

          源碼結(jié)構(gòu)介紹

          首先 pnpm 的代碼主要集中在根目錄下的?packages?目錄中,pnpm 自身所采用的以 pnpm workspace 作為 monorepo 的管理工具,其里面的一些模塊都是作為一個(gè)個(gè)獨(dú)立的子包存在于 packages 目錄下面。

          因?yàn)?pnpm 本身的 monorepo 主要管理的都是一些工具庫(kù)相關(guān)的子包,因此其采用的發(fā)包方案正是 changesets,具體可以參考我之前的文章:?https://mp.weixin.qq.com/s/DkXmsAGcT6_xl?ePYgqy4Rw。

          packages 下各個(gè)子包的具體目錄結(jié)構(gòu)可以參考以下的結(jié)構(gòu):

              .
          // 依賴安裝的核心邏輯代碼包
          ├── core
          // 核心包的日志輸出包
          ├── core-loggers
          // 日志打印的包
          ├── default-reporter
          // 解析依賴包路徑的包(包括軟鏈接的情況等)
          ├── dependency-path
          // filter 邏輯相關(guān)的包
          ├── filter-workspace-packages
          // monorepo 下 package、workspace 相關(guān)的包
          ├── find-packages
          ├── find-workspace-dir
          ├── find-workspace-packages
          // git 相關(guān)的工具包
          ├── git-fetcher
          ├── git-resolver
          // 處理依賴提升的工具包
          ├── hoist
          // 生命周期相關(guān)的包
          ├── lifecycle
          // lock 文件相關(guān)的一些工具包
          ├── lockfile-file
          ├── lockfile-to-pnp
          ├── lockfile-types
          ├── lockfile-utils
          ├── lockfile-walker
          // 處理 npm registry 相關(guān)、以及解析對(duì)應(yīng) npm 包的包
          ├── normalize-registries
          ├── npm-registry-agent
          ├── npm-resolver
          // 處理 cli 參數(shù)相關(guān)的包
          ├── parse-cli-args
          // 解析依賴
          ├── parse-wanted-dependency
          // monorepo 生成依賴圖相關(guān)包
          ├── pkgs-graph
          // plugin-commands 包都是涉及對(duì)應(yīng)子命令邏輯相關(guān)
          ├── plugin-commands-audit
          ├── plugin-commands-env
          ├── plugin-commands-installation
          ├── plugin-commands-listing
          ├── plugin-commands-outdated
          ├── plugin-commands-publishing
          ├── plugin-commands-script-runners
          ├── plugin-commands-server
          ├── plugin-commands-setup
          ├── plugin-commands-store
          // pnpm 整個(gè)項(xiàng)目主包
          ├── pnpm
          // 讀項(xiàng)目 .pnpmfile.cjs 的包
          ├── pnpmfile
          // 讀項(xiàng)目的 pkg.json 工具包
          ├── read-project-manifest
          // 用于提升 pnpm 中的項(xiàng)目依賴的包(類似于 yarn 的方式)
          ├── real-hoist
          // 可視化輸出依賴安裝過(guò)程中的 peerDep 問(wèn)題包
          ├── render-peer-issues
          // 依賴安裝過(guò)程中解析依賴使用
          ├── resolve-dependencies
          //
          ├── resolve-workspace-range
          ├── resolver-base
          // 用于降級(jí)命令到 npm 相關(guān)邏輯的包
          ├── run-npm
          // 根據(jù) pkg-graph 對(duì)子包進(jìn)行排序
          ├── sort-packages
          // 硬鏈接 store 管理相關(guān)的包
          ├── store-connection-manager
          ├── store-controller-types
          // 將依賴添軟鏈接到 node_modules 的包
          ├── symlink-dependency
          // npm 壓縮包的抓取以及解析的包
          ├── tarball-fetcher
          ├── tarball-resolver
          // 寫 pkg.json 的包
          ├── write-project-manifest
          └── ...

          pnpm 本身內(nèi)部有很多的包,上面樹(shù)狀架構(gòu)中,我已經(jīng)省略掉了一些不常用到或者說(shuō)是接近廢棄的包(即便如此,仍然還是存在很多很多的包...)。

          這里我主要根據(jù) pnpm 官網(wǎng)中的各命令行來(lái)對(duì)代碼結(jié)構(gòu)做個(gè)介紹,其實(shí)也有很多命令封裝使用到了相同模塊的代碼。例如?install?、update?、add?等命令。

          主入口

          首先 pnpm 整個(gè)項(xiàng)目的主入口包文件為?packages/pnpm?這個(gè)包里面,這個(gè)包名稱也直接叫做?pnpm?,其中?main.ts?文件是其入口文件,這個(gè)文件會(huì)處理掉用戶傳進(jìn)來(lái)的一些參數(shù),然后根據(jù)處理后的不同的參數(shù)對(duì)各命令做一個(gè)下發(fā)執(zhí)行工作,下發(fā)后的命令參數(shù)再到各個(gè)包里面去,從而執(zhí)行里面對(duì)應(yīng)的邏輯。

          處理參數(shù)用到的包為?@pnpm/parse-cli-args?,它會(huì)接收到用戶傳遞進(jìn)來(lái)的命令行參數(shù),然后將其處理成一個(gè) pnpm 內(nèi)部的統(tǒng)一格式,例如用戶輸入如下命令:

              pnpm add -D axios

          這里傳進(jìn)來(lái)的一些參數(shù)都會(huì)被?parseCliArgs?這個(gè)方法處理:

          例如?add?會(huì)被處理給?cmd?字段,一些裸的參數(shù)例如?axios?會(huì)被放進(jìn)?cliParams?這個(gè)數(shù)組中,-D?這個(gè)參數(shù)在?cliOptions?里面去。處理后的這些變量以及參數(shù)用于主入口文件后續(xù)代碼執(zhí)行邏輯的判斷。具體的判斷邏輯可以在調(diào)試的時(shí)候遇到了,再去看對(duì)應(yīng)的入口邏輯判斷調(diào)試即可,這里不做具體的介紹。

          入口包里還會(huì)用到的內(nèi)部包有?@pnpm/find-workspace-packages?以及?@pnpm/filter-workspace-packages?。

          • findWorkspacePackages?在入口文件中用于找到 pnpm workspace(適用于 monorepo 項(xiàng)目)中所有包的一些信息(例如名稱、路徑等)。

          • filterPackages?相對(duì)而言來(lái)說(shuō)是個(gè)比較關(guān)鍵的包,pnpm 官方有一篇文檔專門介紹了?--filter?這一功能模塊(參考:?https://pnpm.io/filtering),它為幾乎所有的?pnpm 命令提供了一個(gè)很簡(jiǎn)單且實(shí)用的篩選功能,根據(jù)用戶傳遞進(jìn)來(lái)的篩選參數(shù)對(duì) monorepo 下的子包進(jìn)行一個(gè)篩選,會(huì)根據(jù)篩選參數(shù)(例如?...)輸出帥選出來(lái)的對(duì)應(yīng)包以及相關(guān)信息。

          在?main.ts?中會(huì)通過(guò)調(diào)用當(dāng)前包下面的?cmd?目錄下面的方法(pnpmCmds),來(lái)完成各命令的分發(fā)。

          • 如果 cmd 值為?add?、install?、update?等這些涉及和依賴安裝相關(guān)的包,則會(huì)走?@pnpm/plugin-commands-installation?這個(gè)包里面對(duì)應(yīng)的子命令邏輯(基本上 pnpm 所有的核心模塊都圍繞依賴安裝這一塊展開(kāi))。

          • 如果 cmd 值為?pack?、publish?這一類涉及到打包發(fā)布的包,則會(huì)走?@pnpm/plugin-commands-publishing?這個(gè)包的邏輯。

          • 如果 cmd 值為?run?、exec?、?dlx?等這些和命令執(zhí)行相關(guān)的方法,則會(huì)走?@pnpm/plugin-commands-script-runners?這個(gè)包的邏輯。

          這里更多相關(guān)的邏輯參考?pnpm/src/cmd?這一塊的命令掛載詳情。

          下面我會(huì)根據(jù)官網(wǎng)的 CLI commands 來(lái)對(duì)這里面涉及到的邏輯進(jìn)行一個(gè)講解。

          依賴管理

          這部分可以說(shuō)是整個(gè) pnpm 最核心的一部分了,其中涉及到了?pnpm installpnpm add <deps>?等依賴管理相關(guān)的核心命令。

          在上一節(jié)提到這一塊的邏輯主要在 pnpm 下的?@pnpm/plugin-commands-installation?這個(gè)包中完成,這里只是簡(jiǎn)單介紹一下這一塊的邏輯以及引用到的包,并不做具體的討論,因?yàn)殛P(guān)于 pnpm 的依賴安裝原理真的要結(jié)合代碼去介紹原理的話,是可以再去寫一整篇文章的。

          這一塊依賴管理的核心邏輯是在對(duì)應(yīng)包目錄下的?src/installDeps?這個(gè)目錄下,幾乎所有依賴相關(guān)的命令最后的邏輯都會(huì)在這里中轉(zhuǎn)執(zhí)行,可以看到包括?install?、add?、update?命令的核心邏輯都會(huì)在這一塊執(zhí)行。具體還是根據(jù)用戶傳遞進(jìn)來(lái)的參數(shù)進(jìn)行邏輯轉(zhuǎn)換:

               const result = await mutateModules([
          {
          ...mutatedProject,
          dependencySelectors,
          manifest: updatedImporter.manifest,
          peer: false,
          targetDependenciesField: 'devDependencies',
          },
          ], installOpts)

          這里簡(jiǎn)單截取一下對(duì)應(yīng)的依賴安裝執(zhí)行邏輯調(diào)用的方法,這里的?mutateModules?方法來(lái)自于包?@pnpm/core?,該包為整個(gè) pnpm 項(xiàng)目的核心包,一些關(guān)鍵性的核心邏輯(例如依賴安裝等)都是在這里實(shí)現(xiàn),具體看實(shí)現(xiàn)可以參考源碼。

          依賴管理這里還會(huì)涉及到一些其他的包:

          • 用于處理 lifeCycle 方法的?@pnpm/lifecycle

          • 輸出日志(例如依賴安裝過(guò)程中的日志打印)的?@pnpm/core-loggers?、@pnpm/logger

          • 依賴安裝過(guò)程中生成、更新 pnpm-lock.yaml 文件的?@pnpm/lockfile-file

          • 依賴安裝過(guò)程中解析依賴并拉取依賴包的?@pnpm/resolve-dependencies

          之前筆者在調(diào)試?pnpm update?的一個(gè) bug 的時(shí)候,就是從?plugin-command-installation?到?resolve-dependencies?一步步抽絲剝繭,最后找到問(wèn)題出現(xiàn)在一個(gè)庫(kù)函數(shù)的語(yǔ)句處理里面,具體可以參考 pr: https://github.com/pnpm/pnpm/pull/4243。

          調(diào)試技巧

          如果你想調(diào)試 pnpm 的話,其實(shí)在 pnpm 的源碼倉(cāng)庫(kù)下面有個(gè)?CONTRIBUTING.md?文檔,里面比較推薦的方式是使用?pnpm run compile?對(duì)項(xiàng)目子包進(jìn)行一個(gè)整體的編譯,然后通過(guò)?node <repo_dir>/packages/pnpm [command]?的方式進(jìn)行調(diào)試。

          但實(shí)際上這種方式效率比較低下,很多時(shí)候代碼修改了,調(diào)試的時(shí)候并不符合預(yù)期,修改完成之后又需要再次修改代碼進(jìn)行重新編譯。

          之前有一段時(shí)間調(diào)試 pnpm 的經(jīng)歷,這里給大家分享一下我個(gè)人的一些調(diào)試經(jīng)驗(yàn)。

          在?packages/pnpm?的 bin 目錄下有個(gè)?pnpm.cjs?文件,里面的?require?方法指定了 pnpm 在執(zhí)行的時(shí)候走那一塊的邏輯:

          fec1ca185375900a8b64379912ecfe1e.webp

          這里默認(rèn)的邏輯走的是打包后的?dist?目錄下的代碼邏輯,pnpm 的 compile 每次編譯產(chǎn)物的默認(rèn)目錄都是在 dist 目錄,但這里如果只是調(diào)試的話,我們其實(shí)可以完全不走 dist 目錄下的產(chǎn)物代碼邏輯。

          之前筆者給 pnpm 提過(guò)一個(gè) pr,在下面加上了一段用于走本地產(chǎn)物代碼,在上面截圖中也可以看到,這里調(diào)試的時(shí)候只需要注釋掉走?dist?代碼的那段邏輯,然后去走?lib?目錄下的代碼即可:

          7aa458930bd9d33788b8a18ca292c928.webp

          同時(shí)目前基本上 pnpm 下大部分正在維護(hù)的子包使用 typescipt 在開(kāi)發(fā),筆者之前還給一些庫(kù)補(bǔ)上了?tsc --watch?命令:

          10ff97f05146167f0c9a518ea7a4545c.webp

          因此如果想通過(guò)一種即時(shí)編譯的方式去調(diào)試 pnpm 源碼的話,可以直接到對(duì)應(yīng)的子包下面將對(duì)應(yīng)子包的 start 命令給 run 起來(lái)。然后針對(duì)不同的子包去進(jìn)行一個(gè)調(diào)試的工作。以下為筆者的一個(gè)調(diào)試流程,可以提供來(lái)參考。

          調(diào)試流程

          例如調(diào)試 pnpm 下面的一個(gè)子包,以?@pnpm/plugin-commands-installation?為例子。

          首先可以對(duì)整個(gè)包代碼執(zhí)行一次全量的編譯,防止有些包代碼同步之后本地產(chǎn)物沒(méi)更新,直接在整個(gè)項(xiàng)目的根目錄下執(zhí)行一次:

              pnpm run compile

          這次時(shí)間可能會(huì)比較久一點(diǎn),但能保證后面一些被引用到的包且我們不去調(diào)試包的產(chǎn)物是最新的,防止出現(xiàn)一些包出現(xiàn)?require?不到的問(wèn)題。

          然后直接?cd?到需要調(diào)試的包目錄下面,同時(shí)主包也要 run 起來(lái),注意這里要把上一節(jié)提到的入口代碼修改好。這里筆者一般是起多個(gè)終端進(jìn)程,然后將該包的 ts 編譯 run 起來(lái):

              cd packages/pnpm && npm run start
          cd packages/plugin-commands-installation && npm run start

          b690b80437f728d554a3b2050cc7f1fc.webp

          接下來(lái)就可以找個(gè)真實(shí)的 pnpm 項(xiàng)目來(lái)進(jìn)行調(diào)試了。

          例如這里以 naive-ui (https://www.naiveui.com/)這個(gè)項(xiàng)目(使用?pnpm 作為依賴管理)作為例子,這里可以在?plugin-commands-installation?中需要調(diào)試的代碼打上斷點(diǎn),然后通過(guò) vscode 的?debug terminal?來(lái)進(jìn)行調(diào)試:

              # 在調(diào)試項(xiàng)目的目錄下,例如筆者這里是 naive-ui
          node ~/path/to/pnpm/packages/pnpm install

          這樣通過(guò) node 直接到指定的 pnpm 源文件目錄去進(jìn)行調(diào)試,這時(shí)命令就會(huì)分發(fā)到對(duì)應(yīng)代碼邏輯里面去,前面設(shè)置的斷點(diǎn)就會(huì)很快生效。參考如圖:

          d90471e8de658618368a7c5f6d47b257.webp

          這樣就可以相對(duì)簡(jiǎn)潔且能直接針對(duì)源碼進(jìn)行調(diào)試了,如果有代碼修改也可以在源碼里面修改之后直接進(jìn)行調(diào)試。

          不過(guò)這樣調(diào)試也有個(gè)缺點(diǎn),例如調(diào)試依賴層級(jí)比較深的庫(kù)的時(shí)候,會(huì)出現(xiàn)同時(shí)起很多進(jìn)程的現(xiàn)象,例如下圖為筆者調(diào)試 pnpm 依賴安裝流程時(shí),對(duì)各個(gè)庫(kù)進(jìn)行斷點(diǎn)觀察的圖:

          728a6041699e2c841dfb261f138b5182.webp

          圖中一共起了 6 個(gè)進(jìn)程,但總來(lái)說(shuō)的話,還是要比去構(gòu)建產(chǎn)物里面進(jìn)行調(diào)試找問(wèn)題要簡(jiǎn)潔明了得多。

          總結(jié)

          目前 pnpm 已經(jīng)在 2021 年取得了不俗的成績(jī),期待 2022 年這一年同樣也能帶來(lái)更多驚喜的 feature。同時(shí)也期待越來(lái)越多的 contributor 能參與到 pnpm 的源碼建設(shè)中來(lái),一起共同建設(shè)可能是未來(lái)最有前景的包管理工具。

          也喜歡這篇文章能給大家?guī)?lái)收獲,期待越來(lái)越好~

          瀏覽 63
          點(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>
                  一级片日逼片 | 大鸡巴久久久久久久久久久 | 特一级黄色片免费看 | 天天日天天射一区二区三区 | 精品中文字幕在线播放 |