最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一种前端项目依赖管理的未曾设想的道路

    正文概述 掘金(ES2049)   2021-07-16   531

    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 下,都有可能引起工具依赖各种未知的版本漂移,对于整个构建过程的稳定性,都带来极大的挑战。删除重装一时爽,版本不对火葬场。

    另一方面,随着前端项目越来越复杂,越来越多的前端项目,采用 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 的控制下,并不会带来多大的额外开销。

    既然已经有了指导方向,那么我们现在可以开始着手进行具体的改造了:

    首先,最简单快捷的方式,便是将 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
    

    最后,建议将最外层的 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 的依赖管理方式变得脆弱、不可靠。

    前端工程化发展到今天,也面临着越来越多的挑战:

    1. vite 引领的 bundless 潮流下,前端工程化该怎么做?
    2. monorepo 架构下,怎么保证开发、构建的效率?
    3. 微前端架构下,又该如何开发、构建?

    这些新的情况,都是当初设计 node.js 时,人们所未曾面对过的全新情况,我们不能要求 node.js 的设计者在一开始,就把各种情况都考虑的面面俱到,那是不现实的,我们更应该做的,是去分析问题的本质,在前人的肩上更进一步,去找到更适合当前情况下的解决方案。

    本文也只是尝试从 dependency 和 devDependency 入手,来剖析目前使用 node.js 进行前端工程化的一些问题,剖砖引玉,望能给诸位读者带来一些不一样的视角,有任何问题,欢迎在评论区留言,一起探讨:)~


    起源地下载网 » 一种前端项目依赖管理的未曾设想的道路

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元