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

          深入淺出 Yarn 包管理

          共 16686字,需瀏覽 34分鐘

           ·

          2021-06-30 06:15

          關(guān)于yarn

          yarn? 和 npm? 一樣也是 JavaScript? 包管理工具,同樣我們還發(fā)現(xiàn)有 cnpmpnpm? 等等包管理工具,包管理工具有一個就夠了,為什么又會有這么多輪子出現(xiàn)呢?

          為什么是yarn?它和其它工具的區(qū)別在哪里?

          Tip:這里對比的npm是指npm2 ?版本

          npm區(qū)別

          • yarn? 在下載和安裝依賴包采用的是多線程的方式,而 npm? 是單線程的方式執(zhí)行,速度上就拉開了差距
          • yarn? 會在用戶本地緩存已下載過的依賴包,優(yōu)先會從緩存中讀取依賴包,只有本地緩存不存在的情況才會采取遠(yuǎn)端請求的方式;反觀npm則是全量請求,速度上再次拉開差距
          • yarn把所有的依賴躺平至同級,有效的減少了相同依賴包重復(fù)下載的情況,加快了下載速度而且也減少了node_modules的體積;反觀npm則是嚴(yán)格的根據(jù)依賴樹下載并放置到對應(yīng)位置,導(dǎo)致相同的包多次下載、node_modules體積大的問題

          cnpm區(qū)別

          • cnpm國內(nèi)鏡像速度更快(其他工具也可以修改源地址)
          • cnpm將所有項目下載的包收攏在自己的緩存文件夾中,通過軟鏈接把依賴包放到對應(yīng)項目的node_modules

          pnpm區(qū)別

          • yarn一樣有一個統(tǒng)一管理依賴包的目錄
          • pnpm保留了npm2版本原有的依賴樹結(jié)構(gòu),但是node_modules下所有的依賴包都是通過軟連接的方式保存
          從做一個簡單yarn來認(rèn)識yarn

          第一步 - 下載

          一個項目的依賴包需要有指定文件來說明,JavaScript 包管理工具使用 package.json 做依賴包說明的入口。

          {
          ????"dependencies":?{
          ????????"lodash":?"4.17.20"
          ????}
          }

          以上面的 package.json 為例,我們可以直接識別 package.json 直接下載對應(yīng)的包。

          import?fetch?from?'node-fetch';
          function?fetchPackage(packageJson)?{
          ??const?entries?=?Object.entries(packageJson.dependencies);
          ??entries.forEach(async?([key,?version])?=>?{
          ????const?url?=?`https://registry.`yarn`pkg.com/${key}/-/${key}-${version}.tgz`,
          ????const?response?=?await?fetch(url);
          ????if?(!response.ok)?{
          ??????throw?new?Error(`Couldn't?fetch?package?"${reference}"`);
          ????}
          ????return?await?response.buffer();
          ??});
          }

          接下來我們再看看另外一種情況:

          {
          ????"dependencies":?{
          ????????"lodash":?"4.17.20",
          ????????"customer-package":?"../../customer-package"
          ????}
          }

          "customer-package": "../../customer-package" 在我們的代碼中已經(jīng)不能正常工作了。所以我們需要做代碼的改造:

          import?fetch?from?'node-fetch';
          import?fs?from?'fs-extra';
          function?fetchPackage(packageJson)?{
          ??const?entries?=?Object.entries(packageJson.dependencies);
          ??entries.forEach(async?([key,?version])?=>?{
          ????//?文件路徑解析直接復(fù)制文件
          ????if?([`/`,?`./`,?`../`].some(prefix?=>?version.startsWith(prefix)))?{
          ??????return?await?fs.readFile(version);
          ????}
          ????//?非文件路徑直接請求遠(yuǎn)端地址
          ????//?...old?code
          ??});
          }

          第二步 - 靈活匹配規(guī)則

          目前我們的代碼可以正常的下載固定版本的依賴包、文件路徑。但是例如:"react": "^15.6.0" 這種情況我們是不支持的,而且我們可以知道這個表達(dá)式代表了從 15.6.0 版本到 15.7.0 內(nèi)所有的包版本。理論上我們應(yīng)該安裝在這個范圍中最新版本的包,所以我們增加一個新的方法:

          import?semver?from?'semver';
          async?function?getPinnedReference(name,?version)?{
          ??//?首先要驗證版本號是否符合規(guī)范
          ??if?(semver.validRange(version)?&&?!semver.valid(version))?{
          ????//?獲取依賴包所有版本號
          ????const?response?=?await?fetch(`https://registry.`yarn`pkg.com/${name}`);
          ????const?info?=?await?response.json();
          ????const?versions?=?Object.keys(info.versions);
          ????//?匹配符合規(guī)范最新的版本號
          ????const?maxSatisfying?=?semver.maxSatisfying(versions,?reference);
          ????if?(maxSatisfying?===?null)
          ??????throw?new?Error(
          ????????`Couldn't?find?a?version?matching?"${version}"?for?package?"${name}"`
          ??????);
          ????reference?=?maxSatisfying;
          ??}
          ??return?{?name,?reference?};
          }
          function?fetchPackage(packageJson)?{
          ??const?entries?=?Object.entries(packageJson.dependencies);
          ??entries.forEach(async?([name,?version])?=>?{
          ????//?文件路徑解析直接復(fù)制文件
          ????//?...old?code
          ????let?realVersion?=?version;
          ????//?如果版本號以?~?和?^?開頭則獲取最新版本的包
          ????if?(version.startsWith('~')?||?version.startsWith('^'))?{
          ??????const?{?reference?}?=?getPinnedReference(name,?version);
          ??????realVersion?=?reference;
          ????}
          ????//?非文件路徑直接請求遠(yuǎn)端地址
          ????//?...old?code
          ??});
          }

          那么這樣我們就可以支持用戶指定某個包在一個依賴范圍內(nèi)可以安裝最新的包。

          第三步 - 依賴包還有依賴包

          現(xiàn)實遠(yuǎn)遠(yuǎn)沒有我們想的那么簡單,我們的依賴包還有自己的依賴包,所以我們還需要遞歸每一層依賴包把所有的依賴包都下載下來。

          //?獲取依賴包的dependencies
          async?function?getPackageDependencies(packageJson)?{
          ??const?packageBuffer?=?await?fetchPackage(packageJson);
          ??//?讀取依賴包的`package.json`
          ??const?packageJson?=?await?readPackageJsonFromArchive(packageBuffer);
          ??const?dependencies?=?packageJson.dependencies?||?{};
          ??return?Object.keys(dependencies).map(name?=>?{
          ????return?{?name,?version:?dependencies[name]?};
          ??});
          }

          現(xiàn)在我們可以通過用戶項目的 package.json 獲取整個依賴樹上所有的依賴包。

          第四步 - 轉(zhuǎn)移文件

          可以下載依賴包還不夠的,我們要把文件都轉(zhuǎn)移到指定的文件目錄下,就是我們熟悉的node_modules里。

          async?function?linkPackages({?name,?reference,?dependencies?},?cwd)?{
          ??//?獲取整個依賴樹
          ??const?dependencyTree?=?await?getPackageDependencyTree({
          ????name,
          ????reference,
          ????dependencies,
          ??});
          ??await?Promise.all(
          ????dependencyTree.map(async?dependency?=>?{
          ??????await?linkPackages(dependency,?`${cwd}/`node_modules`/${dependency.name}`);
          ????})
          ??);
          }

          第五步 - 優(yōu)化

          我們雖然可以根據(jù)整個依賴樹下載全部的依賴包并放到了node_modules里,但是我們發(fā)現(xiàn)依賴包可能會有重復(fù)依賴的情況,導(dǎo)致我們實際下載的依賴包非常冗余,所以我們可以把相同依賴包放到一個位置,這樣就不需要重復(fù)下載。

          function?optimizePackageTree({?name,?reference,?dependencies?=?[]?})?{
          ??dependencies?=?dependencies.map(dependency?=>?{
          ????return?optimizePackageTree(dependency);
          ??});
          ??for?(let?hardDependency?of?dependencies)?{
          ????for?(let?subDependency?of?hardDependency.dependencies))?{
          ??????//?子級依賴是否和父級依賴存在相同依賴
          ??????let?availableDependency?=?dependencies.find(dependency?=>?{
          ????????return?dependency.name?===?subDependency.name;
          ??????});
          ??????if?(!availableDependency)?{
          ??????????//?父級依賴不存在時,把依賴插入到父級依賴
          ??????????dependencies.push(subDependency);
          ??????}
          ??????if?(
          ????????!availableDependency?||
          ????????availableDependency.reference?===?subDependency.reference
          ??????)?{
          ????????//?從子級依賴中剔除相同的依賴包
          ????????hardDependency.dependencies.splice(
          ??????????hardDependency.dependencies.findIndex(dependency?=>?{
          ????????????return?dependency.name?===?subDependency.name;
          ??????????})
          ????????);
          ??????}
          ????}
          ??}
          ??return?{?name,?reference,?dependencies?};
          }

          我們通過逐級遞歸一層層將依賴從層層依賴展平,減少了重復(fù)的依賴包安裝。截止到這一步我們已經(jīng)實現(xiàn)了簡易的yarn了~

          yarn體系架構(gòu)

          d345e12e7d23ac2afe89c8d03dabf5ad.webp看完代碼后給我最直觀的就是yarn把面向?qū)ο蟮乃枷氚l(fā)揮的淋漓盡致

          • Configyarn相關(guān)配置實例
          • cli:全部yarn命令集合實例
          • registriesnpm源相關(guān)信息實例
            • 涉及 lock 文件、解析依賴包入口文件名、依賴包存儲位置和文件名等
          • lockfileyarn.lock 對象
          • intergrity checker:用于檢查依賴包下載是否正確
          • package resolver:用于解析 package.json 依賴包不同引用方式
            • package request:依賴包版本請求實例
            • package reference:依賴包關(guān)系實例
          • package fetcher:依賴包下載實例
          • package linker:依賴包文件管理
          • package hoister:依賴包扁平化實例
          yarn工作流程

          流程概要

          這里我們已yarn add lodash 為例,看看一下yarn都在內(nèi)部做了哪些事情。yarn在安裝依賴包時會分為主要 5 個步驟:

          • checking:檢查配置項(.yarnrc、命令行參數(shù)、package.json 信息等)、兼容性(cpu、nodejs 版本、操作系統(tǒng)等)是否符合約定
          • resolveStep:解析依賴包信息,并且會解析出整個依賴樹上所有包的具體版本信息
          • fetchStep:下載全部依賴包,如果依賴包已經(jīng)在緩存中存在則跳過下載,反之則下載對應(yīng)依賴包到緩存文件夾內(nèi),當(dāng)這一步都完成后代表著所有依賴包都已經(jīng)存在緩存中了
          • linkStep:緩存的依賴包扁平化的復(fù)制副本到項目的依賴目錄下
          • buildStep:對于一些二進(jìn)制包,需要進(jìn)行編譯,在這一步進(jìn)行

          流程講解

          我們繼續(xù)以yarn add lodash 為例

          初始化

          查找yarnrc 文件

          //?獲取`yarn`rc文件配置
          //?process.cwd?當(dāng)前執(zhí)行命令項目目錄
          //?process.argv?用戶指定的`yarn`命令和參數(shù)
          const?rc?=?getRcConfigForCwd(process.cwd(),?process.argv.slice(2));
          /**
          ?*?生成Rc文件可能存在的所有路經(jīng)
          ?*?@param?{*}?name?rc源名
          ?*?@param?{*}?cwd?當(dāng)前項目路經(jīng)
          ?*/

          function?getRcPaths(name:?string,?cwd:?string):?Array<string>?{
          //?......other?code
          ??if?(!isWin)?{
          ????//?非windows環(huán)境從/etc/`yarn`/config開始查找
          ????pushConfigPath(etc,?name,?'config');
          ????//?非windows環(huán)境從/etc/`yarn`rc開始查找
          ????pushConfigPath(etc,?`${name}rc`);
          ??}
          //?存在用戶目錄
          ??if?(home)?{
          ????//?`yarn`默認(rèn)配置路經(jīng)
          ????pushConfigPath(CONFIG_DIRECTORY);
          ????//?用戶目錄/.config/${name}/config
          ????pushConfigPath(home,?'.config',?name,?'config');
          ????//?用戶目錄/.config/${name}/config
          ????pushConfigPath(home,?'.config',?name);
          ????//?用戶目錄/.${name}/config
          ????pushConfigPath(home,?`.${name}`,?'config');
          ??? //?用戶目錄/.${name}rc
          ????pushConfigPath(home,?`.${name}rc`);
          ??}
          //?逐層向父級遍歷加入.${name}rc路經(jīng)
          ??//?Tip:?用戶主動寫的rc文件優(yōu)先級最高
          ??while?(true)?{
          ????//?插入?-?當(dāng)前項目路經(jīng)/.${name}rc
          ????unshiftConfigPath(cwd,?`.${name}rc`);
          ????//?獲取當(dāng)前項目的父級路經(jīng)
          ????const?upperCwd?=?path.dirname(cwd);
          ????if?(upperCwd?===?cwd)?{
          ?????//?we've?reached?the?root
          ??????break;
          ????}?else?{
          ??????cwd?=?upperCwd;
          ????}
          ??}
          //?......read?rc?code
          }

          解析用戶輸入的指令

          /**
          ?*?--?索引位置
          ?*/

          const?doubleDashIndex?=?process.argv.findIndex(element?=>?element?===?'--');
          /**
          ?*?前兩個參數(shù)為node地址、`yarn`文件地址
          ?*/

          const?startArgs?=?process.argv.slice(0,?2);
          /**
          ?*?`yarn`子命令&參數(shù)
          ?*?如果存在?--?則取?--?之前部分
          ?*?如果不存在?--?則取全部
          ?*/

          const?args?=?process.argv.slice(2,?doubleDashIndex?===?-1???process.argv.length?:?doubleDashIndex);
          /**
          ?*?`yarn`子命令透傳參數(shù)
          ?*/

          const?endArgs?=?doubleDashIndex?===?-1???[]?:?process.argv.slice(doubleDashIndex);

          初始化共用實例

          在初始化的時候,會分別初始化 config 配置項、reporter 日志。

          • config 會在 init 時,逐步向父級遞歸查詢 package.json 是否配置了 workspace 字段
            • Tip:如果當(dāng)前是 workspace 項目則yarn.lock 是以 workspace 根目錄的yarn.lock 為準(zhǔn)
          this.workspaceRootFolder?=?await?this.findWorkspaceRoot(this.cwd);
          //?`yarn`.lock所在目錄,優(yōu)先和workspace同級
          this.`lockfile`Folder?=?this.workspaceRootFolder?||?this.cwd;
          /**
          ?*?查找workspace根目錄
          ?*/

          async?findWorkspaceRoot(initial:?string):?Promise<?string>?{
          ????let?previous?=?null;
          ????let?current?=?path.normalize(initial);
          ????if?(!await?fs.exists(current))?{
          ???? //?路經(jīng)不存在報錯
          ??????throw?new?MessageError(this.reporter.lang('folderMissing',?current));
          ????}
          ????//?循環(huán)逐步向父級目錄查找訪問`package.json`\`yarn`.json是否配置workspace
          ????//?如果任意層級配置了workspace,則返回該json所在的路經(jīng)
          ????do?{
          ???? //?取出`package.json`\`yarn`.json
          ??????const?manifest?=?await?this.findManifest(current,?true);
          ???? //?取出workspace配置
          ??????const?ws?=?extractWorkspaces(manifest);
          ??????if?(ws?&&?ws.packages)?{
          ????????const?relativePath?=?path.relative(current,?initial);
          ????????if?(relativePath?===?''?||?micromatch([relativePath],?ws.packages).length?>?0)?{
          ??????????return?current;
          ????????}?else?{
          ??????????return?null;
          ????????}
          ??????}
          ??????previous?=?current;
          ??????current?=?path.dirname(current);
          ????}?while?(current?!==?previous);
          ????return?null;
          }

          執(zhí)行 add 指令

          • 根據(jù)上一步得到的yarn.lock 地址讀取yarn.lock 文件。
          • 根據(jù) package.json 中的生命周期執(zhí)行對應(yīng) script 腳本
          /**
          ?*?按照`package.json`的script配置的生命周期順序執(zhí)行
          ?*/

          export?async?function?wrapLifecycle(config:?Config,?flags:?Object,?factory:?()?=>?Promise<void>):?Promise<void>?{
          //?執(zhí)行preinstall
          ??await?config.executeLifecycleScript('preinstall');
          //?真正執(zhí)行安裝操作
          ??await?factory();
          ??//?執(zhí)行install
          ??await?config.executeLifecycleScript('install');
          //?執(zhí)行postinstall
          ??await?config.executeLifecycleScript('postinstall');
          ??if?(!config.production)?{
          ????//?非production環(huán)境
          ????if?(!config.disablePrepublish)?{
          ???? //?執(zhí)行prepublish
          ??????await?config.executeLifecycleScript('prepublish');
          ????}
          ????//?執(zhí)行prepare
          ????await?config.executeLifecycleScript('prepare');
          ??}
          }

          獲取項目依賴

          • 首先獲取當(dāng)前目錄下 package.jsondependenciesdevDependenciesoptionalDependencies 內(nèi)所有依賴包名+版本號
            • 因為當(dāng)前為 workspace 項目,還需要讀取 workspace 項目中所有子項目的 package.json 的相關(guān)依賴
            • 如果當(dāng)前是 workspace 項目則讀取的為項目根目錄的 package.json
          //?獲取當(dāng)前項目目錄下所有依賴
          pushDeps('dependencies',?projectManifestJson,?{hint:?null,?optional:?false},?true);
          pushDeps('devDependencies',?projectManifestJson,?{hint:?'dev',?optional:?false},?!this.config.production);
          pushDeps('optionalDependencies',?projectManifestJson,?{hint:?'optional',?optional:?true},?true);
          //?當(dāng)前為workspace項目
          if?(this.config.workspaceRootFolder)?{
          ????//?收集workspace下所有子項目的`package.json`
          ????const?workspaces?=?await?this.config.resolveWorkspaces(workspacesRoot,?workspaceManifestJson);
          ????for?(const?workspaceName?of?Object.keys(workspaces))?{
          ???????? //?子項目`package.json`
          ??????????const?workspaceManifest?=?workspaces[workspaceName].manifest;
          ???????? ?//?將子項目放到根項目dependencies依賴中
          ??????????workspaceDependencies[workspaceName]?=?workspaceManifest.version;
          ???????? //?收集子項目依賴
          ??????????if?(this.flags.includeWorkspaceDeps)?{
          ????????????pushDeps('dependencies',?workspaceManifest,?{hint:?null,?optional:?false},?true);
          ????????????pushDeps('devDependencies',?workspaceManifest,?{hint:?'dev',?optional:?false},?!this.config.production);
          ????????????pushDeps('optionalDependencies',?workspaceManifest,?{hint:?'optional',?optional:?true},?true);
          ??????????}
          ????????}
          }

          resolveStep 獲取依賴包

          1. 遍歷首層依賴,調(diào)用 package resolverfind 方法獲取依賴包的版本信息,然后遞歸調(diào)用 find,查找每個依賴下的 dependence 中依賴的版本信息。在解析包的同時使用一個 Set(fetchingPatterns)來保存已經(jīng)解析和正在解析的 package
          2. 在具體解析每個 package 時,首先會根據(jù)其 namerange(版本范圍)判斷當(dāng)前依賴包是否為被解析過(通過判斷是否存在于上面維護(hù)的 set 中,即可確定是否已經(jīng)解析過)
          3. 對于未解析過的包,首先嘗試從 lockfile 中獲取到精確的版本信息, 如果 lockfile 中存在對于的 package 信息,獲取后,標(biāo)記成已解析。如果 lockfile 中不存在該 package 的信息,則向 registry 發(fā)起請求獲取滿足 range 的已知最高版本的 package 信息,獲取后將當(dāng)前 package 標(biāo)記為已解析
          4. 對于已解析過的包,則將其放置到一個延遲隊列 delayedResolveQueue 中先不處理
          5. 當(dāng)依賴樹的所有 package 都遞歸遍歷完成后,再遍歷 delayedResolveQueue,在已經(jīng)解析過的包信息中,找到最合適的可用版本信息

          結(jié)束后,我們就確定了依賴樹中所有 package 的具體版本,以及該包地址等詳細(xì)信息。

          • 對第一層所有項目的依賴包獲取最新的版本號(調(diào)用 package resolverinit 方法)
          /**
          ?*?查找依賴包版本號
          ?*/

          async?find(initialReq:?DependencyRequestPattern):?Promise<void>?{
          ????//?優(yōu)先從緩存中讀取
          ????const?req?=?this.resolveToResolution(initialReq);
          ????if?(!req)?{
          ??????return;
          ????}
          ????//?依賴包請求實例
          ????const?request?=?new?PackageRequest(req,?this);
          ????const?fetchKey?=?`${req.registry}:${req.pattern}:${String(req.optional)}`;
          ????//?判斷當(dāng)前是否請求過相同依賴包
          ????const?initialFetch?=?!this.fetchingPatterns.has(fetchKey);
          ????//?是否更新`yarn`.lock標(biāo)志
          ????let?fresh?=?false;
          ????if?(initialFetch)?{
          ??????//?首次請求,添加緩存
          ??????this.fetchingPatterns.add(fetchKey);
          ??????//?獲取依賴包名+版本在`lockfile`的內(nèi)容
          ??????const?`lockfile`Entry?=?this.`lockfile`.getLocked(req.pattern);
          ??????if?(`lockfile`Entry)?{
          ????????//?存在`lockfile`的內(nèi)容
          ????????//?取出依賴版本
          ????????//?eq:?concat-stream@^1.5.0?=>?{?name:?'concat-stream',?range:?'^1.5.0',?hasVersion:?true?}
          ????????const?{range,?hasVersion}?=?normalizePattern(req.pattern);
          ????????if?(this.is`lockfile`EntryOutdated(`lockfile`Entry.version,?range,?hasVersion))?{
          ??????????//?`yarn`.lock版本落后
          ??????????this.reporter.warn(this.reporter.lang('incorrect`lockfile`Entry',?req.pattern));
          ??????????//?刪除已收集的依賴版本號
          ??????????this.removePattern(req.pattern);
          ???????? //?刪除`yarn`.lock中對包版本的信息(已經(jīng)過時無效了)
          ??????????this.`lockfile`.removePattern(req.pattern);
          ??????????fresh?=?true;
          ????????}
          ??????}?else?{
          ????????fresh?=?true;
          ??????}
          ??????request.init();
          ????}
          ????await?request.find({fresh,?frozen:?this.frozen});
          }
          • 對于請求的依賴包做遞歸依賴查詢相關(guān)信息
          for?(const?depName?in?info.dependencies)?{
          ??????const?depPattern?=?depName?+?'@'?+?info.dependencies[depName];
          ??????deps.push(depPattern);
          ??????promises.push(
          ????????this.resolver.find(......),
          ??????);
          }
          for?(const?depName?in?info.optionalDependencies)?{
          ??????const?depPattern?=?depName?+?'@'?+?info.optionalDependencies[depName];
          ??????deps.push(depPattern);
          ??????promises.push(
          ????????this.resolver.find(.......),
          ??????);
          }
          if?(remote.type?===?'workspace'?&&?!this.config.production)?{
          ??????//?workspaces?support?dev?dependencies
          ??????for?(const?depName?in?info.devDependencies)?{
          ????????????const?depPattern?=?depName?+?'@'?+?info.devDependencies[depName];
          ????????????deps.push(depPattern);
          ????????????promises.push(
          ??????????????this.resolver.find(.....),
          ????????????);
          ??????}
          }

          fetchStep 下載依賴包

          這里主要是對緩存中沒有的依賴包進(jìn)行下載。

          1. 已經(jīng)在緩存中的依賴包,是不需要重新下載的,所以第一步先過濾掉本地緩存中已經(jīng)存在的依賴包。過濾過程是根據(jù) cacheFolder+slug+node_modules+pkg.name 生成一個 path,判斷系統(tǒng)中是否存在該 path,如果存在,證明已經(jīng)有緩存,不用重新下載,將它過濾掉。
          2. 維護(hù)一個 fetch 任務(wù)的 queue,根據(jù)resolveStep中解析出的依賴包下載地址去依次獲取依賴包。
          3. 在下載每個包的時候,首先會在緩存目錄下創(chuàng)建其對應(yīng)的緩存目錄,然后對包的 reference 地址進(jìn)行解析。
          4. 因為 reference 的地址多種情況,如:npm 源、github 源、gitlab 源、文件地址等,所以yarn會根據(jù) reference 地址調(diào)用對應(yīng)的 fetcher 獲取依賴包
          5. 將獲取的 package 文件流通過 fs.createWriteStream寫入到緩存目錄下,緩存下來的是.tgz 壓縮文件,再解壓到當(dāng)前目錄下
          6. 下載解壓完成后,更新 lockfile 文件
          /**
          ?*?拼接緩存依賴包路徑
          ?*?緩存路徑?+?`npm`源-包名-版本-integrity?+?`node_modules`?+?包名
          ?*/

          const?dest?=?config.generateModuleCachePath(ref);
          export?async?function?fetchOneRemote(
          ??remote:?PackageRemote,
          ??name:?string,
          ??version:?string,
          ??dest:?string,
          ??config:?Config,
          ):?Promise<FetchedMetadata>?
          {
          ??if?(remote.type?===?'link')?{
          ????const?mockPkg:?Manifest?=?{_uid:?'',?name:?'',?version:?'0.0.0'};
          ????return?Promise.resolve({resolved:?null,?hash:?'',?dest,?package:?mockPkg,?cached:?false});
          ??}
          ??const?Fetcher?=?fetchers[remote.type];
          ??if?(!Fetcher)?{
          ????throw?new?MessageError(config.reporter.lang('unknownFetcherFor',?remote.type));
          ??}
          ??const?fetcher?=?new?Fetcher(dest,?remote,?config);
          //?根據(jù)傳入的地址判斷文件是否存在
          ??if?(await?config.isValidModuleDest(dest))?{
          ????return?fetchCache(dest,?fetcher,?config,?remote);
          ??}
          ??//?刪除對應(yīng)路徑的文件
          ??await?fs.unlink(dest);
          ??try?{
          ????return?await?fetcher.fetch({
          ??????name,
          ??????version,
          ????});
          ??}?catch?(err)?{
          ????try?{
          ??????await?fs.unlink(dest);
          ????}?catch?(err2)?{
          ??????//?what?do?
          ????}
          ????throw?err;
          ??}
          }

          linkStep 移動文件

          經(jīng)過fetchStep后,我們本地緩存中已經(jīng)有了所有的依賴包,接下來就是如何將這些依賴包復(fù)制到我們項目中的node_modules下。

          1. 在復(fù)制包之前,會先解析 peerDependences,如果找不到匹配的 peerDependences,進(jìn)行 warning 提示
          2. 之后對依賴樹進(jìn)行扁平化處理,生成要拷貝到的目標(biāo)目錄 dest
          3. 對扁平化后的目標(biāo) dest 進(jìn)行排序(使用 localeCompare 本地排序規(guī)則)
          4. 根據(jù) flatTree 中的 dest(要拷貝到的目標(biāo)目錄地址),src(包的對應(yīng) cache 目錄地址)中,執(zhí)行將 copy 任務(wù),將 packagesrc 拷貝到 dest

          5861f9f4b14e83eb042540d6a4522229.webpyarn對于扁平化其實非常簡單粗暴,先按照依賴包名的 Unicode 做排序,然后根據(jù)依賴樹逐層扁平化

          Q&A

          1.如何增加網(wǎng)絡(luò)請求并發(fā)數(shù)量?

          可以增加網(wǎng)絡(luò)請求并發(fā)量:--network-concurrency <number>

          2.網(wǎng)絡(luò)請求總超時怎么辦?

          可以設(shè)置網(wǎng)絡(luò)請求超時時長:--network-timeout <milliseconds>

          3.為什么我修改了yarn.lock 中某個依賴包的版本號還是不可以?

          "@babel/code-frame@^7.0.0-beta.35":
          ??version?"7.0.0-beta.55"
          ??resolved?"https://registry.`yarn`pkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.55.tgz#71f530e7b010af5eb7a7df7752f78921dd57e9ee"
          ??integrity?sha1-cfUw57AQr163p993UveJId1X6e4=
          ??dependencies:
          ????"@babel/highlight"?"7.0.0-beta.55"

          我們隨機(jī)截取了一段yarn.lock 的代碼,如果只修改 versionresolved 字段是不夠的,因為yarn還會根據(jù)實際下載的內(nèi)容生成的 integrityyarn.lock 文件的 integrity 字段做對比,如果不一致就代表本次下載是錯誤的依賴包。

          4.在項目依賴中出現(xiàn)了同依賴包不同版本的情況,我要如何知道實際使用的是哪一個包?

          首先我們要看是如何引用依賴包的。前置場景:

          首先我們根據(jù)當(dāng)前依賴關(guān)系和yarn安裝特性可以知道實際安裝結(jié)構(gòu)為:

          |[email protected]
          |[email protected]
          |[email protected]
          |[email protected]
          |[email protected]
          |[email protected]
          • 開發(fā)同學(xué)直接代碼引用 D 實際為[email protected]
          • B 代碼中未直接聲明依賴 C,但是卻直接引用了 C 相關(guān)對象方法(因為 B 直接引用了 D,且 D 一定會引用 C,所以 C 肯定存在)。此時實際引用非[email protected],而是引用的[email protected]
            • 因為 webpack 查詢依賴包是訪問node_modules下符合規(guī)則的依賴包,所以直接引用了[email protected]

          我們可以通過yarn list 來檢查是否存在問題。

          參考資料

          [1]

          yarn官網(wǎng):?https://www.yarnpkg.cn/

          [2]

          我 fork 的yarn源碼加了部分中文注釋: https://github.com/supergaojian/%60yarn%60

          [3]

          從源碼角度分析yarn安裝依賴的過程: https://jishuin.proginn.com/p/763bfbd29d7e

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(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>
                  黑人借宿与人妻羽月希 | 强奸乱伦大杂烩 | 日韩美女一级a黄片 | 北条麻妃av在线播放 | 人成视频在线观看 |