你可能不知道的Node.js和NPM

前言的前言

这篇文章来自内部分享,目的是让大家会用并且用好Node.js和NPM,毕竟会用和用好还有很大的距离。

另外就是让大家从每天都打交道的工具中听出来新东西。

前言

今天跟大家分享一些实用但很少有资料一次性说清楚的知识,不限于Node.js和NPM,包含一些常识性的知识,会对大家工作学习有比较大的帮助。

希望达到的目标是大家能把Node.js和NPM用好。最不济的情况是遇到问题的时候能知道往哪个方向查。

先做一个小调查。

大家在用哪个版本的Node.js?

有没有4.x的?

有没有5.x的?

有没有7.x的?

有没有9.x的?

有没有11.x的?

有没有12.x的?

我刚来那会儿(2018年5月)看大家有用Node.js6的,有用Node.js7的,有用Node.js8的,有用Node.js9的,还有用Node.js10的,再后来也看见了有用Node.js11的。

现在咱们在okt里做了Node.js版本的限制,环境统一也能帮助大家提升效率,尤其是多个人合作的时候。

基础常识

  1. 高版本一定比低版本好吗?

答:显然不是,为什么?稳定的才是最好的,三天两头环境出问题谁受得了?没法开开信心的干活了。

  1. 什么是LTS版本?

答:英文全称Long Term Support,即长期支持版,从名字就可以看出支持的时间会更长一些,通常几年。而非长期支持版维护时间可能几天到几个月,甚至不维护。不会修bug,没有安全支持。所以生产环境一定要选择长期支持板。当然不是所有软件都有LTS的概念。

  1. LTS与Alpha、Beta、RC什么关系?

答:没有直接关系,他们两个不是一个维度的概念。

Node.js 与 NPM 什么关系?

NPM是Node.js的包管理器,类似于Java中的Maven,PHP中的Composer。一般会跟随Node.js一同安装。

NPM是否是唯一的包管理器呢?答案是否定的,起码还有yarn吧。

Node.js

下面开始Node.js部分。

如何选择版本

下面的几张图是从 Node.js Releases List 截取的,咱们应该选择哪个版本?



当然是选择最新的LTS版,也就是10.14.1。

有没有发现Node.js9没有发布LTS版本?是不是他忘记了?我们发个邮件提醒一下好不好?

其实这就是Node.js的发版规则,只有偶数版本才会发布LTS版,而且他还有个名字。

LTS的名字是怎么来的?

这就得介绍一位大牛,对,门捷列夫,名字是门捷列夫取的。其实是用了化学元素来命名,门捷列夫那个年代哪里有Node.js。没有命名的版本不是LTS版。

io.js是怎么回事儿?

刚才的那个io.js是个什么东西?为啥会在Node.js的列表中?

历史上Node.js闹过分家,后来在大家的妥协下又合并到了一起。

4.0.0版本的Node.js已经不是原来的Node.js了,而是io.js改名的产物。

LTS的一生

首先想一个问题,长期到底是多长?

是不是我选择了长期支持版就意味着我可以一直使用下去了?当然不是,长期不等于无限期。

我们来看Node.js的发版计划,Release Schedule。第一张精度高,第二张图形象,大家喜欢看哪张?

其实每年四月都会有一个偶数的版本被发布,六个月后,也就是当年的十月发布LTS,这时我们就可以使用了。

LTS的前18个月是积极维护期,所有bug都会修复,所有的安全问题都会修复,而且还可能会有一些性能改进加进来。当然这些都是向后兼容的,大家可以放心大胆的升级。

18个月以后进入维护期,或者叫维持期,时长12个月,这个阶段只修复关键bug和关键安全问题,也就是说小问题就不修复了,这时候就得开始考虑升级了。

这样算下来一个偶数版本历时三年,其中两年半是LTS。

每年都会发布一个LTS版本,那也就是说同一时刻会有两到三个LTS版本,这是为了给大家留出了升级的时间。如果时间没有重叠大家是不是就没有时间升级了。

另外,大家有没有发现Node.js8比其他版本维护的时间短?这个是有实际困难在的,Node.js8所使用的OpenSSL到明年十二月就不维护了,Node.js8也就不维护了,比正常少了四个月。所以现在最建议大家使用的是Node.js10的长期支持版,刚两个月,还可以用接近两年半。其次是Node.js8,还有四个多月的积极维护期,Node.js6已经处在维持期,新安装刘不要使用了,如果想体验新功能用Node.js11,实际工作记得切换回去。

如下是寿终正寝的版本,大家就不要在把他们挖出来了。

如何快速切换Node.js版本?

如果想灵活的切换Node.js版本我们有nvm和n可供选择。nvm是用shell编写的,不依赖Node.js环境,n本身是一个Node.js模块,用n得先有Node.js,大家自己灵活选择。

NPM

下面正式开始NPM部分。

npm init

npm init用于初始化一个package,默认以交互式运行,生成package.json和一些简单的字段。

这个很简单,多说一句,npm init是可以自定义初始化模板的,咱们用不到,因为咱们有okt。

依赖包安装

依赖管理是npm的核心功能,执行npm install时会将dependencies 和 devDependencies中列出的模块安装到./node_modules中。

执行npm install <package name>会将指定依赖包安装到./node_modules

执行npm install -g <package name会将依赖包安装到全局。

一、 dependencies 与 devDependencies 什么区别?

答:如果你开发了一个package,别人通过npm install <your package name>安装,那么npm会自动帮你安装dependencies中列出的依赖,而不会帮助你安装devDependencies中列举的模块。

二、那么问题来了,你写了一个React组件,你应该把React写到dependencies还是 devDependencies

答:都不是

三、什么是peerDependencies

答:一般在插件或者指定框架的组件开发时,不应该直接依赖插件的宿主,而是应该声明自己所依赖的环境。peerDependencies就是干这件事儿的,所以上一个问题有答案了。

四、 什么是module?

答:在node_modules中可以被require的目录或者文件。

这样说是不是有点抽象,我们分开来说,满足下面条件之一的就是一个合法的module

  • 包含package.json的目录,并且package.json中包含main字段。
  • 包含index.js的目录。
  • JavaScript文件。

五、 什么是package?

答:包含package.json的目录(文件夹)或者文件。

是不是还是有点抽象,我们也分开说,满足下面条件之一的就是一个合法的pacakge。

  • a) 包含package.json文件作为程序描述的目录。
  • b) 把(a)gzip后得到的压缩文件。
  • c) 能访问到(b)的URL。
  • d) 发布到registry上的<name>@<version>格式。
  • e) 指向(d)的<name>@<tag>格式,tag对应一个版本号。
  • f) 格式,是(e)的<name>@latest简写。
  • g) 一个clone后满足(a)的git url。

六、 可以如何共享package?

知道什么是package了我们就来研究下我们可以如何共享package。

  1. 通过本地目录共享

我们来看实的例子。

~/tmp/test_module目录下创建package.json文件,内容如下

{
"name": "test_module",
"version": "1.0.0",
"description": "",
"main": "index.js"
}

在该目录下创建index.js,在文件内编写相应代码。

在测试项目~/workspace/test下执行

$ npm i --save file:~/tmp/test_module

查看~/workspace/test/package.json,如下

{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"test_module": "file:../../tmp/test_module"
}
}

可以发现新增的dependencies,指向的起本地的一个目录。

查看node_modules目录可以发现新增了一个test_module的module,仔细看你会发现这个一个连接。

  1. 通过本地文件共享

~/tmp/test_module打包成gzip包。

$ tar -zcvf test_module.tar.gz test_module

在测试项目~/workspace/test1下执行

$ npm i --save file:~/tmp/test_module.tar.gz

查看package.json,如下

{
"name": "test1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"test_module": "file:../../tmp/test_module.tar.gz"
}
}

查看node_modules会发现test_module的存在,但是这种情况test_module不再是连接,也就是说npm做了解压,使用的是解压后的结果。

  1. 通过http server分享

这种方式就是上一种方式的另一种形式,只不过由file协议编程了http协议,没什么可特殊说明的。

  1. 通过公共npm源分享

这个是大家最常见的方式,执行npm i <package name>时默认就是从公共npm源获取的,也就是npmjs.com。

  1. 通过私有npm源分享

很多公司为了方便分享私有npm package或者提高安装速度会搭建私有npm源。

  1. 通过git仓库分享
<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]

<protocol>git、git+ssh、git+http、git+https或者git+file之一。

如果提供#<commit-ish>npm将clone指定的提交。如果 commit-ish 是#semver:<semver>格式(<semver>可以是任何合法semver版本号),npm将会去匹配版本号对应的tags或者refs。如果没有提供#<commit-ish>或者 #semver:<semver>npm将会使用master分支。

举例

git+ssh://git@github.com:npm/cli.git#v1.0.27
git+ssh://git@github.com:npm/cli#semver:^5.0
git+https://isaacs@github.com/npm/cli.git
git://github.com/npm/cli.git#v1.0.27

npm i -g 全局安装做了一件什么事儿?

npm i将package安装到当前目录下的node_modules中,而npm i -g 则将包安装到 /usr/local/lib/node_modules下(mac),全局安装的package一般都会提供一个或者几个命令,比如我比较喜欢的serve包。原理是什么在下面讲。

package.json中的bin

如果你的package想提供可执行的命令那么你需要在package.json中指定一个bin字段,类似如下

{
"bin": {
"mycli": "./cli.js"
}
}

其中mycli就是你要提供的命令,而当前路径下的cli.js是真正执行的文件。

对于全局安装npm会在安装时在/usr/local/bin下创建一个名字为mycli的连接链接到/usr/local/lib/node_modules/<your package>/cli.js,这样你在全局安装后命令直接就是可用的。

对于非全局安装,npm不会把bin链接到/usr/local/bin,而是会将命令放到node_modules/.bin下并且连接到./node_modules/<your package>/cli.js。此时我们不能直接使用命令,但是我们可以通过./node_modules/.bin/mycli的方式使用,这样很麻烦。后来从npm5.2开始npm加入了npx,直接npx mycli完成命令执行。

npm scripts

刚刚介绍了bin,知道本地安装会有./node_modules/.bin目录,而npm scripts在命令查找是会优先查找,./node_modules/.bin目录,这也是为啥我们没有全局安装,我们在命令好直接运行会提示命令找不到,但是在npm scripts中却可以运行的原因。

npm install是如何工作的?

这里主要写的是npm2、npm3、npm5、npm6。

为啥没有npm1?那是一个很古老的故事了,从npm2说起就很能说明问题了。

为啥没有npm4?npm4只在Node.js7中使用过,而通过前面的知识我们已经知道7不会长期支持,没关注,确实了解的也不多。

一、npm2

npm2 会采用简单粗暴的递归方式安装,递归dependenciesdevDependenciespeerDependencies

假设packageA和packageB都依赖了packageC的0.1.0版本,安装的结果是node_modules下包含packageA和packageB,而packageA和packageB的node_modules下都各自有一个packageC,属于同一个版本的两个拷贝。

这样设计缺点很明显,可能有很多包被重复安装了很多次,那个时候node_modules很容易特别大,而且层级特别深的情况下windows会遇到路径超长的问题,windows目录长度大约是256的长度。

二、npm3

为了解决npm2的弊端,结合node的module查找机制,npm3采用了将依赖包打平的做法。还是上面的例子,这时的node_modules下存在是是 packageA、packageB、packageC。

另一个改变是不再安装peerDependencies中的依赖,而是会给一个警告。

打平也带来了一个弊端,就是我们没法直观的在node_modules下看到我们的依赖了,不怕,我们有npm ls

另外一个问题是如果packageA依赖packageC的1.0.0版本,而packageB依赖packageC的2.0.0版本,这两个版本不兼容,这时查看node_modules会发现目录下有packageA、packageB、packageC的1.0.0版本,而packageC的2.0.0版本在packageB的node_modules下。这也可以看出,npm3追求的不是觉得对打平,而是尽可能的打平。

三、npm5

npm5跟随Node.js8发布,引入了package-lock.json,作用是锁定模块的版本,而且是递归锁定的。为什么叫锁定,因为它不是简单的版本一致,还包括你从哪个npm源下载的,这个包对应的hash是多少,再次安装时会做完全匹配。

这样就能保证同一个项目每个人安装的版本是一致的。之前的自动升级机制确实很有一些问题。问题是有一些人不按照semver来升级版本,即便是按照这个规则也会有一些不可避免的失误,这就可能导致生产环境和本地环境有差异,还有就是每个人自己的开发环境有差异。

npm5之前的版本也具备锁定版本的能力,有一个shrinkwrap命令。

既然锁定了版本那么如何升级呢? 推荐的方式是npm i <package name>@<version>,这样package.json和package-lock.json都会升级,把升级之后的文件提交到代码库即可。

npm5还引入了一个功能就是npm i自动将依赖包加入到dependencies中。

四、npm6

npm6没有什么对npm install的改进,主要是在安全方面的改进,这里就不提他了。

npm link是另外一个实用功能,如果你开发一个模块(假设为test_module),还不到发布的时候,需要在项目或者另一个模块中使用怎么办?

可以按照如下步骤操作,

  1. 在test_module下执行 npm link
  2. 在使用的项目目录下执行 npm link test_module

这样test_module就以连接的形式链接到了项目目录下的node_modules中。结合前面package的知识,这个连接是一个合法的module。因为是连接,在package中编辑node_modules实时生效。Web IM就用了这样的方案。如果你的包开发到一半为了调试发布了,别人安装了怎么办?那样会引起很多不可控制的问题。

npm配置

我们可以通过 npm config set 修改npm配置,可以通过npm config delete 删除配置,可以通过npm config get 查看配置。这些都是命令形式的。

其实npm是有配置文件,我们可以在我们自己的项目下创建.npmrc文件来给项目定制配置,比如指向内网的npm源。当前用户的跟目录也会有自己.npmrc文件,项目中的优先级更高。 之前我在群里发了一个配置文件,大家可以把他放到自己的项目里。

为什么你在npm install的时候要加sudo?

原因你是把你的文件系统权限搞坏了,好的习惯是不要乱加sudo。如果你已经搞坏了,可以通过chown来修复这个问题,就是把node_modules转交给你自己。

升级Node.js和npm后会遇到啥问题?

可能会发现包不兼容等奇怪问题

$ npm cache clean --force
$ rm -rf node_modules
$ npm i

总结

时间关系,这里不能一一列举,关于更多npm的具体使用大家可以去翻看npm文档。另外我也希望我能抽时间多帮大家看一下各个项目,发现一些问题并改进,帮助大家不断提高效率。

Q & A

接下来是答疑环节,希望以上的内容大家都能理解并能灵活使用,看看有什么问题。

参考链接

  1. npm-package.json

X Y Problem

定义

提问者问了一个自认为可以解决问题的方案,而不是直接提问实际要解决的问题。这实际上浪费了提问和提供帮助双方大量的时间。

  1. 某人想做X
  2. 他不知道怎么做X,但是他觉得Y是一个可行的方案
  3. 但是他也不知道Y怎么做
  4. 他找人问Y问题怎么解决
  5. 帮助他的人觉得Y问题很奇怪
  6. 在浪费的很多时间之后帮助他的人明白他实际想解决X,而Y方案实际上又解决不了X问题

例子

  1. 小明想获取文件的扩展名
  2. 小明认为截取文件名的最后三个字符就是文件扩展名
  3. 小明不知道如何截取最后三个字符
  4. 小明问大明如何截取最后三个字符
  5. 大明告诉了小明如何截取最后三个字符
  6. 小明程序运行不正确
  7. 小明觉得大明给的方案有问题
  8. 小明又来问大明
  9. 反复沟通后大明告诉了小明扩展名可能不止是最后三个字符
  10. 大明帮助小明正确的获取到扩展名

如何避免

  1. 告诉帮助你的人实际要解决的问题
  2. 给帮助你的人足够的信息
  3. 提供你尝试失败的方案给对方

来源

来自http://xyproblem.info,有修改。

Lua学习笔记(基础入门)

简介

  1. lua是一个轻量小巧的脚本语言,编译后一百多K
  2. 用标准C编写
  3. 以源码形式发布
  4. 设计目标是为了嵌入其他程序
  5. 支持面向对象
  6. 自动内存管理
  7. 可以扩展其他软件的能力,如nginx(openresty)

环境安装

mac

1
$ brew install lua

执行

1
$ lua test.lua

基本语法

注释

单行

1
-- 单行注释

阅读全文 »

web前端应届生技术图谱

上篇回顾

上一篇写了一些注意事项。

正文

这篇文章主要帮助应届生梳理一下应该具备的技能,希望可以帮助即将毕业的朋友们。面试找工作和考试不一样,没有大纲,也不是只学好学校书本上知识就OK。主要是下面这张图,后面会有适当的文字用来解释,方便大家理解。


阅读全文 »

使用cnpmjs.org搭建私有npm源

前言

淘宝NPM 让很多技术同学过上了爽快的日子,因为访问npmjs.com有时候真的很慢很慢,真的感叹阿里的财大气粗。而且cnpm还开源了,网址为cnpm/cnpmjs.org 。很多公司使用cnpmjs.org搭建了自己的私有npm源,如美团。也包括okcoin。下面简要的写一下步骤。

正题

首先要选择版本,选择的是2.19.4,是最新的稳定版。从tag来看alpha发到alpha 15版本,beta没发,但是npm上的最新版本的tag确实beta1,这里实在没明白。

cnpmjs需要数据库支持,我们选择MariaDB,安装什么的不说,现在假设已经有一台MySQL可用。

我们选择下载源码部署,把代码clone到本地,并从tag 2.19.4创建一个分支。

阅读全文 »

从零开始搭建一个前端资源自动化构建发布系统(下)

前言

接上篇从零开始搭建一个前端资源自动化构建发布系统(下)。

上一篇写到发布,还需要提供两个功能。

查看构建日志

查看构建日志是一个在正常不过的功能,既然能查看构建日志那么之前就要记录日志,记录日志起码得有程序执行到哪步,是执行成功了还是失败了。

查看日志提供一个web页面,方便使用者查看即可。

构建结果通知

这个是重点,及时反馈给用户是很重要的,通知的方式可以是钉钉、企业微信、邮箱,无论如何让用户实时知道结果。对接方式也不会太复杂,找到对应的API发消息给他即可。

阅读全文 »

从零开始搭建一个前端资源自动化构建发布系统(中)

前言

接上篇从零开始搭建一个前端资源自动化构建发布系统(上),本来想写一个长篇,这个世界好玩儿的东西太多,前段时间在干别的,上一篇在我的草稿箱躺了很久,所以分成了多篇。

上篇说到提交代码要进行构建和发布,前提是分支或者tag格式满足条件,但是不能不分青红皂白直接构建和发布,需要做一些检查。

构建前的检测

  1. 检测当前group是否开通了发布权限,这个是为了清楚的知道哪些用户在使用,方便以后有变更能清晰的知道应该通知哪些人。

    阅读全文 »

从零开始搭建一个前端资源自动化构建发布系统(上)

前言

前端在工程化这条路上已经走了很远了。我用了约两周的时间写出了一个构建发布系统,包括研究方案和解决问题,技术的世界就是这么神奇,想想很简单,动起手来才知道有坑。技术选型当然是JavaScript,放在Node.js这个runtime上执行。

之前写了使用gitlab ci实现前端资源自动发布,本文是算是第二季,所以根本就不是从零开始,标题党了一下。

好用的发布系统应该是什么样子的。

  1. 提交代码能自动构建并且部署到测试环境。
  2. 打tag可以自动构建并将静态资源发布到CDN,技术同学把对应的入口页面或者版本号拿去发布就可以了。

好处

  1. 可以省去技术或者测试同学去干一些重复的活。提交代码不算,这个现在来看必要的。
  2. 每个版本都保留了tag。我们应该为每个版本保留一个tag,这个是不应该省的。
  3. 将权限校验转嫁给了gitlab,因为你没权限提交不了代码嘛。

    阅读全文 »

使用gitlab ci实现前端资源自动发布

描述

web端的特点是灵活,随时可上线。带来的问题就是上线重复次数特别多,这个需要简化。

现在很多公司在内网搭起了自己的gitlab。

如何能省事儿呢?代码提交后依靠gitlab的ci来自动build代码并部署是希望达到的效果。毕竟我现在提交代码大部分时候就两条命令。

1
2
3
$ gcam <msg>

$ gp

这是用了Oh My Zsh 的git插件的效果。

可以做这样一个约定,提交代码到gitlab,如果提交的分支是一个特殊格式的分支名那么静态资源发布系统自动拉取代码build,并且将build的结果发布到日常环境供测试同学来使用。如果是打一个特殊格式的tag那么自动build发布到CDN。

阅读全文 »