共计 5241 个字符,预计需要花费 14 分钟才能阅读完成。
内容简介(不便想要疾速理解文章内容论断的同学)
- 先上论断,Node.js 将依赖分为 dependency 与 devDependency 两局部,然而却专用同一个 node_modules 文件夹的形式,在当下越来越简单的前端我的项目开发过程中,曾经不实用了。
- 工具依赖与业务依赖共用同一个 node_modules 文件夹,会使得开发与构建过程变得低效与软弱,包含以下问题:node_modules 臃肿低效、版本漂移、Monorepo 架构不适配、CI 构建过程软弱等。
- 解决办法是关注点拆散,将业务依赖和工具依赖独自搁置,一个思路是业务依赖纳入版本管理系统,工具依赖进行 precompile,并由专门的构建团队进行公布,开发者能够抉择版本进行全局装置。
node_modules 现状
这张图想必前端同学都不生疏,以后吐槽的 node_modules 的依赖问题,从 2020 年回过头来看,不仅没有解决,反而越来越显著。咱们看很多包的时候都是,“WTF,我啥时候装置过这个依赖?”的状态,大家能够看看本人前端我的项目外面的 node_modules,没有 500M 都不好意思说本人是做前端的,而在这些依赖当中,有多少是真的要用在最终产品外面的依赖呢?又有多少是开发过程、构建过程中,工具的依赖呢?
笔者做了一个简略的试验:
- 独自装置 React 和 ReactDOM,只占用 3.9M 空间。
- 独自装置 Vue,也只占用 3.6M 空间。
- 应用 create-react-app 创立一个空白 React 我的项目,占用 189.6M 空间。
- 应用 vue-cli 创立一个空白 Vue 我的项目,占用 164.5M 空间。
尽管不能代表全副状况,然而想必也能反映一些问题,大家能够回顾一下本人的开发过程中用到了哪些工具:打包工具、开发服务器、测试框架、各种 linters、TypeScript 编译器、Babel 等等。这些工具又都有本人的依赖,子又生孙,孙又生子,子子孙孙无穷尽也,很快啊,你的硬盘就装不下了。而这些工具依赖,只是在开发和构建过程中应用,甚至是在不同的阶段才会应用,比方很多单元测试,其实是在线上 CI 的过程才会跑,然而却都会一股脑儿的装进 node_modules 文件夹里,和业务依赖搅在一起。
DevDependency 带来的问题
工具依赖与业务依赖共用 node_modules,带来的不仅是文件夹莫名增大,npm install 迟缓的问题,同时更会带来依赖版本漂移,引起各种莫名其妙的 BUG。
前端工程倒退到明天,曾经进入一个复杂度暴涨的时代,这是因为前端要解决的资源品种暴涨带来的,不同的资源,又须要不同的工具来进行解决,再叠加上前端技术的高速迭代,5 年的工夫,构建工具就从 Grunt、Gulp 变动到了 Webpack、Parcel、Rollup,将来更有 Vite、Snowpack、Esbuild 等,这样高速的工具更新,再乘上资源品种的增长,带来的是工具复杂度的急速晋升,同时也带来对于工具版本控制的强烈依赖。而在目前 semantic version 的治理办法下,一个小小的业务依赖的 npm install 下,都有可能引起工具依赖各种未知的版本漂移,对于整个构建过程的稳定性,都带来极大的挑战。删除重装一时爽,版本不对火葬场。
对于 Lock 我要再说两句,Lock 的初衷是好的,心愿可能通过 Lock 文件解决依赖版本不统一的问题,然而大家在应用过程中,想必都遇到过 npm install 新包的时候,和 Lock 文件抵触的状况,这时候怎么办呢?删除 node_modules,重新安装呀,那么祝贺你,喜提版本漂移大礼包~
另一方面,随着前端我的项目越来越简单,越来越多的前端我的项目,采纳 Monorepo 的架构,并且须要通过线上的 CI 流程,进行公布,而当初的 devDependency 的设计形式,并不能适应于这样的构建形式。
my-mono-repo
├── package.json
└── packages
├── A
│ ├── package.json
│ ├── node_modules
│ └── src
├── B
│ ├── package.json
│ ├── node_modules
│ └── src
└── C
├── package.json
├── node_modules
└── src
先说说 Monorepo 的问题,下面是一种很天然的目录设计,A、B、C 各自有各自的 node_modules,而这就带来一个问题,devDependency 装置在哪里,如果装置在各自的 node_modules,那么大量的空间理论被冗余的工具依赖占据,如果说要对立装置在父目录的 node_modules 里,那么又须要解决解决不同子目录依赖的版本问题,即便能够应用 lerna 等工具进行主动的治理,在子目录下的 npm install 也有可能引起父目录中某些独特依赖的版本漂移,对其余子目录的开发、构建引入未知的变动。
除了不实用 Monorepo 架构之外,在线上 CI 的过程中,devDependency 的设计也会带来各种问题。首先,冗余的 node_modules 带来的是对于空间和网络更大的开销,使得 CI 过程中环境初始化的过程更长,其实整个 CI 过程中,并不会用到 devDependency 中的所有工具依赖,比方打包、Lint、测试等过程,依赖的工具都不一样,然而每一步都须要下载全量的 devDependency;另一方面,业务依赖的降级,也往往使得工具依赖在人不知; 鬼不觉中被动降级,从而导致之前缓存的 devDependency 生效,如果没有及时清空缓存,更新版本,很容易导致构建与开发环境的不统一,引起未知的版本问题。这所有脆弱性的源头,都在于目前前端我的项目的复杂性,曾经超过了当初设计的 devDependency 的负载,把 devDependency 和 dependency 不加区分的都放在 node_modules 外面,就像打鸡蛋的时候,把鸡蛋壳也搅进去了,而后还得把鸡蛋壳从打好的蛋液里挑出来,无奈~
未曾构想的路线
写到这里,咱们曾经谈了很多 devDependency 带来的问题,那么咱们如何解决这些问题呢?
首先,咱们先要定义问题的根本原因是什么,这里我间接说出我的论断,这所有问题的起因,在于工具依赖与业务依赖未做到关注点拆散。如果大家有一些其余编程语言的应用教训,能够回忆一下,无论是 Python,还是 Java、C++,素来都没有将工具依赖与业务依赖混装在一起过,这是因为两者的作用、更新频率、应用要求,都不一样,对于业务依赖,咱们最终是要集成进产品中去的,是带有业务属性的,须要可能及时解决业务问题,更新频率上会频繁一些,尤其是在当初 Monorepo 和公有 NPM 流行的当下;而对于工具依赖,咱们的需要是稳固、对立、高效,并不需要频繁的变更,或者说即便变更,也应该对业务开发者是无感和通明的,更不能因为业务依赖的变更,就导致工具的不稳固。
既然咱们找到了问题的本源,那么咱们的解决方案就不言而喻了:
一方面,对于 devDependency 的工具依赖,咱们将其从 node_modules 外面拆离进去,更进一步,咱们能够把这些工具依赖封装成一个团队专属的 build 工具,而后每个业务开发的同学只须要将其装置到全局,在本人的我的项目里甚至连 Babel 都不须要,就装置几个业务上须要的依赖,这样的开发体验,岂不爽哉!对于封装的工具,能够交给专门的构建小组进行保护,甚至能够封装成二级制的包,比方采纳 pkg、deno compile 更进一步的进步效力。
另一方面,对于 dependency 的业务依赖,咱们能够持续留在 node_modules 外面,更进一步,咱们能够将 node_modules 纳入到 git 的版本控制中。因为工具依赖曾经拆离进来了,剩下的都是业务依赖,原本就是要构建到最终产品中的,咱们须要保障在各个环境中的强一致性,同时拆离了工具依赖的 node_modules 大小也会降到一个正当的程度,纳入到 git 的管制下,并不会带来多大的额定开销。
对于把 node_modules 纳入 git 的治理,是否会使得开销过大,这里咱们能够构想一下,在任何一个长期运行的我的项目中,业务依赖绝对于自有代码,最多比例也就在 1:1,不可能会呈现在一个成熟的商业我的项目中,本人写的代码还没有引入的依赖大,同时因为业务依赖最终是要打包成产品,公布到网上的,所以咱们也有能源,去最大水平上缩减业务依赖的大小。综上所述,将业务依赖纳入版本治理的老本,绝对于带来的强一致性的益处来说,是能够承受的。
既然曾经有了领导方向,那么咱们当初能够开始着手进行具体的革新了:
首先,最简略快捷的形式,便是将 dependency 和 devDependency 别离拆分到两个 package.json 中,而后将 devDependency 的目录构造晋升一个档次,利用 node.js 的模块层层向上查找的个性,根本不须要改变任何代码,即可实现对于 dependency 和 devDependency 的拆分,具体目录构造如下
|-- node_modules # 装置 devDependency 的依赖
|-- package.json # 记录 devDependency 的依赖
|-- myApp
|-- node_modules # 装置 dependency 的依赖
|-- src # 业务代
|-- package.json # 记录 dependency 的依赖
|-- .gitignore
接着,咱们在 .gitignore 文件中,排除掉装置 devDependency 依赖的 node_modules,而装置 dependency 依赖的 node_modules 则须要保留在 git 仓库中,具体内容如下
node_modules
!myApp/node_modules
这里将 dependency 依赖纳入 git 治理,有利有弊,害处是会导致 git clone 下载的文件变大,益处是一方面咱们能够通过 git 来保障业务依赖的强一致性,只有从同一个分支 checkout 进去的代码,业务依赖肯定是齐全一样的,另一方面如果有同学新增或者批改了业务依赖,也可能被 git 进行记录下来,做到变更的可追溯化,更进一步,还能够针对这种状况,进行专门的依赖评审,这在之前只是改改 package.json 就能够变更业务依赖的时候,是很容易就被疏忽掉的,因而绝对于大小问题,纳入 git 带来的稳定性与一致性的收益在我看来会更大一些。
最初,倡议将最外层的 package.json 中依赖库的版本锁定,或者交由专门的同学进行对立治理,业务的同学只须要关怀本人的业务依赖。
当然,以上的计划只是最简略的革新,次要是为了给大家一个能够参考的思路,根本思维就是关注点拆散,工具的归工具,业务的归业务,对于不同我的项目的理论状况,大家能够在以上思路的根底上,更进一步的摸索,找到最合乎本人团队的保护形式。
对将来的一点瞻望
在前端工程化的倒退过程中,node.js 的作用堪称居功至伟,甚至能够说,正是有了 node.js,才真正率领前端走到了工程化的畛域,以前尽管也有通过 Java 或者 Python 来解决前端代码的利用,然而对于前端程序员来说,须要再把握另一门语言,始终总是感觉隔着一层窗户纸,而将这层窗户纸捅破的正是 node.js。
咱们不应该漠视 node.js 对于前端工程化带来的奉献,同时咱们也要意识到 node.js 在设计上的局限性,毕竟最后 node.js 的设计目标,无论是 Common.js 的模块标准,还是 module.paths 的依赖门路查找形式,在最后设计时,更多是在为了应用 node.js 进行服务端编程服务的,其应用的 dependency 和 devDependency 的依赖装置形式,也并不是专门为了前端工程化来设计的,这导致的一个问题就是,咱们在享受 node.js 带来的工程化的能力时,也因为前端我的项目自身的特点,使得间接采纳 node.js 的依赖治理形式变得软弱、不牢靠。
前端工程化倒退到明天,也面临着越来越多的挑战:
- vite 引领的 bundless 潮流下,前端工程化该怎么做?
- monorepo 架构下,怎么保障开发、构建的效率?
- 微前端架构下,又该如何开发、构建?
这些新的状况,都是当初设计 node.js 时,人们所未曾面对过的全新状况,咱们不能要求 node.js 的设计者在一开始,就把各种状况都思考的八面玲珑,那是不事实的,咱们更应该做的,是去剖析问题的实质,在前人的肩上更进一步,去找到更适宜当前情况下的解决方案。
本文也只是尝试从 dependency 和 devDependency 动手,来分析目前应用 node.js 进行前端工程化的一些问题,剖砖引玉,望能给诸位读者带来一些不一样的视角,有任何问题,欢送在评论区留言,一起探讨:)~
作者:ES2049 | 魔力圈圈
文章可随便转载,但请保留此原文链接。
十分欢送有激情的你退出 ES2049 Studio,简历请发送至 mailto:caijun.hcj@alibaba-inc…