自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

是否應(yīng)該使用 Barrel Files 管理不同目錄的導(dǎo)出結(jié)構(gòu)?

開(kāi)發(fā) 前端
模塊化是一種非常重要且有用的技術(shù),早期的 ECMAScript 規(guī)范一直被詬病缺乏模塊化能力,所有變量和函數(shù)都是全局的,這非常容易導(dǎo)致名稱(chēng)沖突、代碼污染等問(wèn)題,導(dǎo)致這時(shí)候的 Javascript 語(yǔ)言根本無(wú)法支撐起大規(guī)模項(xiàng)目開(kāi)發(fā)。

這是一個(gè)很糾結(jié)的問(wèn)題:是否應(yīng)該使用 Barrel Files 管理不同目錄的導(dǎo)出結(jié)構(gòu)? 我個(gè)人曾經(jīng)非常推崇這種編碼模式,畢竟這確實(shí)是一種簡(jiǎn)單但非常便于管理模塊之間依賴(lài)關(guān)系的方法,但經(jīng)過(guò)長(zhǎng)久實(shí)踐后,發(fā)現(xiàn)潛在的弊端遠(yuǎn)遠(yuǎn)大于收益,因此強(qiáng)烈建議大家從此刻開(kāi)始,停止使用 Barrel File,具體原因且聽(tīng)我娓娓道來(lái)。

Barrel File 是什么

模塊化是一種非常重要且有用的技術(shù),早期的 ECMAScript 規(guī)范一直被詬病缺乏模塊化能力,所有變量和函數(shù)都是全局的,這非常容易導(dǎo)致名稱(chēng)沖突、代碼污染等問(wèn)題,導(dǎo)致這時(shí)候的 Javascript 語(yǔ)言根本無(wú)法支撐起大規(guī)模項(xiàng)目開(kāi)發(fā),為此開(kāi)源社區(qū)及 ECMA 組織前后產(chǎn)出 CMD、UMD、ESM 等模塊化方案。模塊化能力使得開(kāi)發(fā)能夠基于模塊粒度做好耦合度與內(nèi)聚性管理,模塊之間劃定好交互與邊界,彼此獨(dú)立互不侵?jǐn)_。某種程度上,這使得 Javascript 從簡(jiǎn)單的腳本語(yǔ)言晉升為具備大規(guī)模開(kāi)發(fā)能力的現(xiàn)代化編程語(yǔ)言。

但是,隨項(xiàng)目規(guī)模增長(zhǎng)新的問(wèn)題接踵而至,模塊數(shù)量增長(zhǎng)容易導(dǎo)致模塊之間的依賴(lài)關(guān)系變得復(fù)雜,特別在大型項(xiàng)目中,可能需要橫跨多層目錄結(jié)構(gòu)后才能引用到目標(biāo)模塊,例如在下面的項(xiàng)目結(jié)構(gòu)中:

src/
├── components/
│   ├── Button/
│   │   ├── Button.ts
│   │   └── index.ts
│   ├── Input/
│   │   ├── Input.ts
│   │   └── index.ts
│   └── Modal/
│       ├── Modal.ts
│       └── index.ts
├── utils/
│   ├── format.ts
│   └── validate.ts
└── services/
    ├── api/
    │   ├── userApi.ts
    │   └── index.ts
    └── auth/
        ├── authService.ts
        └── index.ts

假設(shè) src/components/Button/Button.ts 模塊需要使用 src/services/api/auth/authService.ts 模塊,則相關(guān)導(dǎo)入語(yǔ)句:

// src/components/Button/Button.ts
import { authService } from '../../services/auth/authService';

這種方式存在許多缺點(diǎn):

  • 可讀性差:隨著目錄層級(jí)的增加,引用路徑會(huì)變得越來(lái)越長(zhǎng)和復(fù)雜,這不僅降低了代碼的可讀性,還增加了理解代碼結(jié)構(gòu)的難度;
  • 強(qiáng)耦合:Button 強(qiáng)依賴(lài)于 authService 文件所在的相對(duì)路徑,目錄層級(jí)間邊界模糊不清;
  • 維護(hù)成本高:在大型項(xiàng)目中,隨著模塊和文件數(shù)量的增加,維護(hù)相對(duì)路徑變得更加困難,任何一次目錄結(jié)構(gòu)的調(diào)整都可能需要大量的路徑更新工作。

所幸這個(gè)問(wèn)題并不難解決,常見(jiàn)解題思路有 alias 與 Barrel Files:

  • alias:使用構(gòu)建工具 —— 如 Typescript 的alias指定路徑別名:
// tsconfig.json
{
"compilerOptions": {
  "paths": {
    "@services/*": ["src/services/*"]
  }
}
}

之后即可簡(jiǎn)化引用方式為:

import { authService } from '@services/auth/authService';
  • Barrel Files:設(shè)置Barrel Files統(tǒng)一導(dǎo)出模塊,如:
// services/index.ts
export { authService } from './auth/authService';

之后即可簡(jiǎn)化引用方式為:

import { authService } from '../../services';


PS:alias 模式也同樣存在許多影響工程可維護(hù)性的細(xì)微問(wèn)題,此處先按下不表。

結(jié)合上面的示例,Barrel files 本質(zhì)上就是一種聚合多個(gè)模塊并統(tǒng)一導(dǎo)出的編碼模式,我們可以代碼文件夾中創(chuàng)建一個(gè) Barrel File,通過(guò)該文件統(tǒng)一導(dǎo)出可用模塊,外部模塊在消費(fèi)時(shí)只需引用到 Barrel 文件即可,無(wú)需關(guān)心代碼文件夾內(nèi)部細(xì)節(jié),這會(huì)帶來(lái)一些好處:

  • 引用方無(wú)需感知依賴(lài)模塊的具體文件結(jié)構(gòu),達(dá)到簡(jiǎn)化導(dǎo)入語(yǔ)句,在大型工程中這有利于提升開(kāi)發(fā)效率;
  • Barrel Files 有助于管理模塊的可見(jiàn)性,對(duì)外屏蔽不必要的細(xì)節(jié),從而降低模塊間耦合;
  • 模塊之間通過(guò) Barrel Files 解耦后,后續(xù)更容易做重構(gòu),例如重命名、移動(dòng)文件等,都只需要修改 barrel files 即可;
  • 使用 Barrel Files 可以統(tǒng)一模塊的導(dǎo)出方式, 使代碼的結(jié)構(gòu)和導(dǎo)入方式更加一致和規(guī)范,便于團(tuán)隊(duì)協(xié)作。

如果嚴(yán)格遵循這種模式,只要確保 Barrel File 文件對(duì)外暴露內(nèi)容的穩(wěn)定性,文件夾內(nèi)部無(wú)論怎么騰挪轉(zhuǎn)移,上層甚至無(wú)需同步做出重構(gòu)。

這聽(tīng)著很美好,那么問(wèn)題在哪呢?

問(wèn)題:

1.Tree-Shaking 失效

這是 Barrel Files 模式最嚴(yán)重的問(wèn)題:使用 Barrel Files 容易導(dǎo)致 Tree-shaking 失敗。

Tree-shaking 是前端構(gòu)建工具提供的非常基礎(chǔ)而實(shí)用的性能優(yōu)化特性,其底層依賴(lài)于 ESM 模塊規(guī)范的靜態(tài)特性,在構(gòu)建過(guò)程中通過(guò)追蹤分析各模塊導(dǎo)入導(dǎo)出結(jié)構(gòu),刪除無(wú)用模塊,達(dá)到性能優(yōu)化效果。

但是,在使用 Barrel Files 模式時(shí),情況發(fā)生了變化,舉例來(lái)說(shuō),假設(shè)項(xiàng)目結(jié)構(gòu)如下:

src/
├── components/
│   ├── Button.js
│   ├── Input.js
│   └── index.js  // Barrel file
└── index.js

對(duì)應(yīng)核心代碼:

// src/components/Button.js
export const Button = () => {
  console.log("Buttond");
};

// src/components/Input.js
class SingleTon { // 這里是重點(diǎn)
  constructor() {
    console.log("SingleTon");
  }
}

export const instance = new SingleTon();

export const Input = () => {
  console.log("input");
};

// src/components/index.js
export { Button } from "./Button";
export { Input } from "./Input";

// src/index.js
import { Button } from "./components";

Button();

結(jié)果來(lái)看,entry 文件 src/index.js 僅消費(fèi)了 Button 函數(shù),但構(gòu)建結(jié)果卻是:

原因很簡(jiǎn)單,構(gòu)建工具認(rèn)為 SingleTon 是一段有 sideEffects 的代碼,出于安全考慮不予刪除。在 Barrel Files 模式下,這意味著下游模塊所有被判定為帶有 sideEffects 的代碼都會(huì)被保留下來(lái),導(dǎo)致最終產(chǎn)物可能被打入許多無(wú)用代碼。注意,有許多代碼模式會(huì)被判定為具有 sideEffects,包括:

  • 頂層函數(shù)調(diào)用,如:console.log('a')。
  • 修改全局狀態(tài)或?qū)ο螅纾篸ocument.title = 'new Title'。
  • IIFE 函數(shù)。
  • 動(dòng)態(tài)導(dǎo)入語(yǔ)句,如:import('./mod')。
  • 原型鏈污染,如:Array.prototype.xxx = function (){xxx}。
  • 非 JS 資源:Tree-shaking 能力僅對(duì) ESM 代碼生效,一旦引用非 JS 資源則無(wú)法樹(shù)搖;
  • 等等;

這些都是非常常見(jiàn)的編碼模式,特別是非 JS 資源,在前端項(xiàng)目中通過(guò) import/require 引用樣式、多媒體文件是非常常見(jiàn)的,但在 Barrel File 模式下卻容易打入不必要代碼。例如擴(kuò)展上述示例,引入 Less 文件:

即使 s 并未被消費(fèi),產(chǎn)物中依然帶有 input.module.less 代碼,以及對(duì)應(yīng) CSS module 運(yùn)行時(shí)代碼。

嚴(yán)格來(lái)說(shuō),并不單純是 Barrel Files 模式導(dǎo)致 tree-shaking 失效,而是 Barrel Files 疊加 sideEffects 的判定邏輯導(dǎo)致部分場(chǎng)景下樹(shù)搖失敗。那么相對(duì)的,假如放棄 Barrel Files 模式(雖然這會(huì)給損害 DX),直接引用具體模塊代碼,必然也就不會(huì)帶入其他無(wú)用模塊的 sideEffects。

2.循環(huán)引用

循環(huán)引用是指兩個(gè)或多個(gè)模塊相互依賴(lài),形成一個(gè)閉環(huán),例如,模塊 A 引用了模塊 B,而模塊 B 又引用了模塊 A。而 Barrel Files 模式又非常容易導(dǎo)致循環(huán)引用結(jié)構(gòu),例如對(duì)于下面的項(xiàng)目結(jié)構(gòu):

src/
├── components/
│   ├── Button.ts
│   ├── Input.ts
│   └── index.ts  // Barrel 文件
└── index.ts
// Button.ts

import { Input } from './index';

export function Button() {
  Input();  
}
// Input.ts

import { Button } from './index';

export function Input() {
  Button();  
}

這里面,Barrel File 模式看似隱蔽了 Button 與 Input 模塊的實(shí)現(xiàn)細(xì)節(jié),降低兩者耦合,但依賴(lài)關(guān)系并沒(méi)有消失而是發(fā)生轉(zhuǎn)移,兩者的循環(huán)依賴(lài)從直接變成間接,以人類(lèi)的認(rèn)知能力而言變得相對(duì)隱晦而難以察覺(jué),這只是一個(gè)簡(jiǎn)單示例,當(dāng)項(xiàng)目規(guī)模增長(zhǎng)十倍、百倍時(shí),循環(huán)依賴(lài)的概率也會(huì)相應(yīng)大幅增長(zhǎng)。

這種依賴(lài)結(jié)構(gòu)是非常脆弱不健康的,容易進(jìn)一步引發(fā)許多工程問(wèn)題:

模塊未定義問(wèn)題:當(dāng)出現(xiàn)循環(huán)引用時(shí),某些模塊可能會(huì)在未完全定義之前被使用,導(dǎo)致 undefined 錯(cuò)誤。例如:

// Button.ts
import { Input } from './index';

console.log(Input);  // 可能是 undefined

程序崩潰或行為異常:循環(huán)引用會(huì)導(dǎo)致模塊加載順序問(wèn)題,驗(yàn)證時(shí)可能引發(fā)程序崩潰或行為異常。例如:

// Button.ts
import { Input } from './index';

export function Button() {
  Input();  
}

// Input.ts
import { Button } from './index';

export function Input() {
  Button();  // Button 與 Input 遞歸調(diào)用,導(dǎo)致程序死循環(huán)
}

構(gòu)建困難:“如何構(gòu)建循環(huán)依賴(lài)”是一個(gè)非常復(fù)雜的問(wèn)題,業(yè)界并沒(méi)有對(duì)此形成統(tǒng)一規(guī)范,各家構(gòu)建工具的處理邏輯都有所不同,致使某些代碼在當(dāng)下看似可用,但換一個(gè)構(gòu)建環(huán)境可能出現(xiàn)各種細(xì)微問(wèn)題;

調(diào)試?yán)щy:循環(huán)引用導(dǎo)致的問(wèn)題往往隱蔽且難以調(diào)試。開(kāi)發(fā)者需要深入理解模塊加載順序,才能找到并修復(fù)問(wèn)題。

幸運(yùn)的是,這類(lèi)問(wèn)題相對(duì)容易檢測(cè),社區(qū)有不少工具可用于輔助檢測(cè)循環(huán)依賴(lài),常見(jiàn)如 eslint-plugin-import 的 no-cycle 規(guī)則,接入成本低,但其內(nèi)部實(shí)現(xiàn)需要向下遍歷被依賴(lài)模塊,IO 與 CPU 都比較密集,有較高性能成本,官方文檔也警告過(guò)需要關(guān)注性能問(wèn)題:

其次,更推薦使用 oxlint 的 import/no-cycle 規(guī)則,由于底層是 rust 實(shí)現(xiàn)的,執(zhí)行性能要比 eslint-plugin-import 插件高出不少,使用方法:

npm i -g oxlint@latest
echo '{"rules": {"import/no-cycle": "error"}}' > .oxlintrc.json
oxlint -c .oxlintrc.json --quiet --import-plugin .

3.影響部分工程化工具性能

這里有一個(gè)基礎(chǔ)前提:Barrel Files 模式容易引入無(wú)用代碼,無(wú)用是指代碼被定義、導(dǎo)入?yún)s從未被業(yè)務(wù)系統(tǒng)消費(fèi),但這些無(wú)用代碼卻是實(shí)實(shí)在在影響著許多工程工具的執(zhí)行性能,包括但不限于:Typescript、VS Code、Vitest、Webpack、RSPack、ESLint 等等。

以 VS Code 為例,不同導(dǎo)入風(fēng)格最終需要處理的空間復(fù)雜度差異極大,以 antd 為例:

  • 使用 Barrel Files 時(shí):

對(duì)應(yīng) TS Server 日志,需要處理許多無(wú)關(guān)模塊:

  • 直接引用模塊:

對(duì)應(yīng) TS Server 日志,只需處理 affix 模塊即可:

類(lèi)似的,使用 Barrel Files 時(shí),tsc 也需要消費(fèi)更多的時(shí)間索引那些根本不會(huì)被消費(fèi)的文件:

  • 使用 Barrel Files 時(shí):

  • 直接引用模塊:

從 540ms 到 141ms,兩者相差接近 4 倍的性能開(kāi)銷(xiāo),本質(zhì)上,這是因?yàn)?Typescript 并沒(méi)有智能到能夠識(shí)別出 Barrel Files 導(dǎo)入的無(wú)用模塊,tsc 或 ts server 會(huì)忠實(shí)的解析編譯所有遇到的模塊及子模塊,結(jié)果,Barrel Files 模式使用的越多,越容易造成不必要的性能浪費(fèi)。

類(lèi)似的,這一問(wèn)題在 Webpack/RSPack 等構(gòu)建工具,或 bundle 中不使用 tree-shaking 時(shí),或者 Vitest 等工具中同樣存在,都會(huì)導(dǎo)致大量無(wú)效計(jì)算。

4. 模塊間依賴(lài)關(guān)系變得更復(fù)雜

在使用 Barrel Files 后,對(duì)引用方而言確實(shí)無(wú)需關(guān)注具體模塊文件路徑,模塊之間的依賴(lài)規(guī)則似乎變得更簡(jiǎn)單些,但事實(shí)是,復(fù)雜度不會(huì)消失,只是轉(zhuǎn)嫁到 Barrel Files 上而已,凌亂的關(guān)系最終匯聚到 Barrel Files 上反而可能使得最終的模塊關(guān)系圖變得愈加復(fù)雜:

  • 依賴(lài)關(guān)系更隱蔽:Barrel 文件會(huì)隱藏模塊之間的直接依賴(lài)關(guān)系,使得依賴(lài)關(guān)系變得不透明。例如,在 Home.ts 文件中,我們通過(guò) Barrel 文件導(dǎo)入了 Button 和 Input 組件,但實(shí)際上我們并不能直觀理解這些組件具體來(lái)自哪里,而這會(huì)使得代碼調(diào)試變得復(fù)雜晦澀;
  • 增加了不必要的依賴(lài):由于 Barrel Files 會(huì)導(dǎo)出所有包含的模塊,有時(shí)明明不存在消費(fèi)行為,但通過(guò) Barrel Files 搭橋后,反而導(dǎo)致模塊之間增加不必要的依賴(lài)關(guān)系;

舉個(gè)實(shí)際例子,開(kāi)源工具 mswjs 曾經(jīng)做過(guò)一次重構(gòu),移除倉(cāng)庫(kù)內(nèi)部分 Barrel Files,重構(gòu)之前模塊之間的依賴(lài)關(guān)系:

重構(gòu)之后:

變得肉眼可見(jiàn)的清晰明了。復(fù)雜依賴(lài)關(guān)系會(huì)帶來(lái)許多可讀性問(wèn)題,提高代碼理解成本,即使借助編程工具如 VS Code,過(guò)度復(fù)雜的關(guān)系也會(huì)讓人難以理解全貌。

最佳實(shí)踐

綜上,雖然 Barrel Files 確實(shí)能簡(jiǎn)化導(dǎo)入路徑,降低模塊耦合,提升開(kāi)發(fā)體驗(yàn),但代價(jià)卻是犧牲了產(chǎn)物與工程環(huán)境性能,且長(zhǎng)期來(lái)看反而會(huì)讓整體模塊依賴(lài)關(guān)系變得復(fù)雜難懂,我認(rèn)為應(yīng)該盡量克制使用 Barrel Files,使用其他方法替代,如:

  • 若項(xiàng)目文件結(jié)構(gòu)比較簡(jiǎn)單,建議直接引用具體模塊;若文件結(jié)構(gòu)過(guò)于復(fù)雜,請(qǐng)重構(gòu),在 Monorepo 語(yǔ)境下做好拆包分解;
  • 如果你正在開(kāi)發(fā) NPM Package,可使用 package.json 的 exports、typesVersion 等字段聲明導(dǎo)出內(nèi)容,以此替代 Package 的 index 文件;

其次,應(yīng)該設(shè)置一些 Lint 檢測(cè)規(guī)則預(yù)防出現(xiàn)意料之外的 Barrel 文件,常用規(guī)則包括:

  • 使用 eslint-plugin-import 或 oxlint 的 no-cycle 規(guī)避循環(huán)引用;
  • 編寫(xiě) ESLint 規(guī)則禁止 export */import * 一類(lèi)代碼,規(guī)避過(guò)度開(kāi)放的 Barrel Files,不過(guò)社區(qū)似乎還沒(méi)有想過(guò)實(shí)現(xiàn),后續(xù)有機(jī)會(huì)再將我們內(nèi)部實(shí)現(xiàn)的版本開(kāi)源出去吧。
責(zé)任編輯:姜華 來(lái)源: Tecvan
相關(guān)推薦

2013-04-16 15:49:31

iOS開(kāi)發(fā)是否用ARC

2018-11-21 09:38:52

企業(yè)云計(jì)算優(yōu)化

2013-09-30 09:49:06

存儲(chǔ)網(wǎng)絡(luò)FCoE存儲(chǔ)

2011-01-10 10:30:05

linux目錄結(jié)構(gòu)

2014-04-28 16:13:11

Unix目錄結(jié)構(gòu)

2012-02-08 09:48:25

開(kāi)源項(xiàng)目

2011-07-14 09:27:07

2012-08-03 10:36:29

2010-05-27 11:12:10

SVN目錄結(jié)構(gòu)

2011-11-09 10:40:39

2012-03-08 10:36:55

2010-05-26 19:05:06

SVN庫(kù)

2013-01-17 15:26:21

Android工程目錄結(jié)構(gòu)Android開(kāi)發(fā)

2011-06-10 09:27:19

iOS 5Twitter

2013-08-28 10:18:21

創(chuàng)業(yè)

2010-04-19 09:29:49

2013-10-30 22:50:30

Clouda結(jié)構(gòu)

2011-05-31 13:12:15

Android 目錄結(jié)構(gòu)

2010-01-05 17:52:34

JSON形式

2010-04-25 23:13:26

活動(dòng)目錄物理結(jié)構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)