前端十万个为什么(之一):我们为什么需要npm?
如果我能比别人看得更远,那是因为我站在巨人的肩上。
——艾萨克·牛顿
现代前端开发已经离不开Node了。大家都知道在安装Node时会附赠一个命令行工具Node Package Manager,即npm。或许你已经照着教程输入过好多遍”npm install xxx”,并且你发现npm的命令林林总总几十条,package.json的配置项令人眼花缭乱,但不知你有没有认真想过,我们为什么需要npm?如果没有它,世界会怎样?
我的理解,npm所做的一切都是为了解决软件工程界一个一直以来的追求:代码复用。抓住这个核心,也就抓住了正确理解和使用npm的钥匙。
为什么要复用代码呢?因为基于已有的成熟代码快速开发新的应用,可以极大地提高开发效率,正所谓“站在巨人肩膀上”“不要重复造轮子”。
So,在Node环境下要复用JS代码,我们有哪些方案呢?
1. 刀耕火种——copy&paste
复制粘贴代码的思路很直接,但如今还在的这样搞的同学应该是从原始社会穿越来的吧。。这个方案最大的缺点倒还不是代码冗余,而是一旦所复制的原始代码发生了变化,那就必须手动修改每一处复本,在稍有规模的项目里根本不可行。
2. 耕牛犁地——CommonJS
Node实现了一个模块系统CommonJS,实在是JSer的一大福音。借助它,我们不必再复制粘贴代码了:假如一个作者开发了一个名为lib1的库,他只需代码写在一个名叫lib1.js的文件里,用module.export语句导出;而使用者只需把lib1.js下载到自己工程目录,require一下便可直接用啦!(此时lib1也被称为一个“依赖”)
但这里仍然存在两个大问题:
一,如果lib1.js本身也复用了别的代码,比如lib2.js、lib3.js...那你在下载lib1.js的时候,必须手动把它所依赖的这些模块文件也一并下载;可要是lib2.js还依赖lib5.js、lib6.js....呢?一棵庞大的、深不见底的依赖树很难手工管理。
二、lib1.js的作者修复了几个bug,但没有一个机制能让他通知你升级旧的模块文件。
作为一名职业素养良好的程序员,看到这些问题的第一反应是不是“写个脚本”?哈哈,不用麻烦了,因为已经有人替我们写好了,这个脚本工具就是npm。
3. 机械化耕作——npm
有了上面“自力更生”的原始体验,再看看npm提供的依赖安装、卸载、升级、发布等一条龙服务,是不是很爽?
Npm制定了一个包规范,所谓规范就是一些格式和约定,比如约定从package.json文件里读取这个包的所有信息,包括它的名字、版本号、它依赖于哪些别的包等;又比如约定node_modules目录专门用来存放第三方依赖,Node为此提供的支持是内置的require方法默认会到这个目录下去检索模块,而无需手动指定路径。有了这些规范,一个包的开发、依赖安装、发布等都步骤都标准化了,省心省力。
可以说,JavaScript从一门“玩具”语言,到如今可以胜任大型项目开发,模块化和npm是其进化路上的重要一步。
后记
大家都知道前端有“三板斧”,但刚才我一直在谈JS,完全没提到另外两板斧。这是因为npm只是“Node模块管理器”,Node上又没有HTML和CSS,npm自然管不到。那么除了可以直接支持Node端的开发以外,npm又如何为浏览器端开发提供支持呢?
答案是:npm生态圈提供了很多强大的前端开发工具,比如Webpack、Babel、ESLint等。特别是Webpack、Browserify、rollup这类构建工具,可以接手浏览器端的依赖管理重任,以及很多其他的附加功能。这些内容,且待下回分解。。