目录
一 介绍
Node.js是一个开源的、跨平台的JavaScript运行时环境,使JavaScript可以运行在浏览器端之外;Node.js是事件驱动的架构,支持异步IO,单线程;Node.js实现了CommonJS的模块加载功能(当然也支持ES6模块语法)。
但是开发者常用Node.js制作进行前端开发的工具(如Gurnt、Gulp和Webpack),进行前端开发。
二 模块
模块(module)是一个可复用的功能模块,一个模块对应一个Js文件,模块可导出模块内的变量、函数、对象给其他模块使用。
一个package(包)可含有一个或多个module,但只暴露某个模块(通过package.json
中的main
属性)。package.json
记录该包的所有属性,如包依赖、作者等。
把package看成是module也行,毕竟只暴露一个文件的导出,在被引入时表现得像本地模块一样,
2.1 module
模块通过module.exports
导出对象(exports
),通过require
获得对象(exports
),下面直接看模块的实现原理。
2.1.1 module wrapper
假设一个模块:
const PI = 3.14159265359;
exports.area = radius => (radius ** 2) * PI;
exports.circumference = radius => 2 * radius * PI;
其他模块中要引入该模块时,会用一个匿名函数包裹该模块:
(function (exports, require, module, __filename, __dirname) {
module.exports = exports = {};
// Your module code ...
const PI = 3.14159265359;
exports.area = radius => (radius ** 2) * PI;
exports.circumference = radius => 2 * radius * PI;
});
当require
一个模块时,会执行该wapper函数,得到模块导出的对象module.exports
。
可以看出exports
与module.exports
指向同一个对象,但最终导出的是module.exports
对象。因此如果直接exports=obj
赋值对象,由于是引用传值的,并不会修改module.exports
导致该对象没有被导出,需要使用module.exports=obj
。
wrapper函数的参数module
不是全局的,每个模块都有一个module
,它除了包含模块导出对象exports
外,还包含id、parent、children等等。
剩下的__filename
表示当前模块文件路径;__dirname
表示模块文件所处的目录路径;require
是用来导入其他模块的函数。
2.1.2 require
使用方式如:
const fs = require('fs');
fs.readFile('./file.txt', 'utf-8', (err, data) => {
if(err) { throw err; }
console.log('data: ', data);
});
require()
函数返回的是被导入模块导出的对象exports
。函数参数传入模块的文件名(无后缀),它会安装下面的方式查找模块:
- 内置模块:查找Node.js内置模块(如fs)
- 社区共享的modules:查找
node_modules
文件夹下的package的文件夹名。即传入的是package名,它会返回该package出口文件(main
属性指定)的导出对象。如果知道该package中某个文件具体的名字,可直接引入,如
packageName/fileName.js
- 本地模块:如果
require
参数名以./
,/
或../
开始,它会在当前目录查找本地模块。
把package看成是module也行,毕竟只暴露一个文件的导出,在被引入时表现得像本地模块一样
2.1.3 exports
没啥好讲的,2.1.1小节讲完了。
2.2 package
一个package包含多个module,使用package.json记录该package关键的信息。
package.json一些字段如下:
main
:指定该package的入口文件,当require(packageName)
时,会导出package入口文件的exports
。scripts
:定义脚本命令,通过npm run
来执行。dependencies
:该package依赖的其他包devDependencies
:开发时需要的依赖包。
package安装有全局安装(npm install --global
)和局部安装(默认)两种方法。
- 全局安装:package会被安装到
/usr/lib/node_modules
或/usr/local/lib/node_modules
目录下。package如果有可执行脚本的话,会被添加环境变量下,因此可以直接执行它(如npm
)。package下的模块不能直接require
全局安装的模块,需要使用npm link
创建符号链接。 - 局部安装(默认):package会被安装到当前package的
node_modules
目录下。如果有可执行文件,会被放入node_modules/.bin
目录下,需要使用npx
执行,它会自动在对应文件夹中寻找。
默认下,局部安装时,安装的依赖会被记录在package.json
文件的dependencies
字段中,添加选项--save-dev
可将依赖记入在devDependencies
字段中。
之所以可以直接执行Node.js脚本,是因为在linux环境中,解析型脚本在首部添加了
#!/usr/bin/env Interpreter
,它会在执行时使用指定解析器。
三 使用
3.1 安装Node.js
可从Long Term Support (LTS) version of Node中查看Node目前长期支持版本(LTS),最好选择v10.15.3
版本的。在download page中下载Node.js;对于Linux,使用包管理器安装,参考package manager
我使用的是WSL的Ubuntu,安装命令如下:
# Using Ubuntu
curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs
安装时,它会自己全局安装npm包,并且该包含有可执行脚本,因此可以使用npm命令
3.2 npm init && node
尽管node
命令可以直接执行js脚本(如node index.js
),但最好还是使用npm init
初始化产生package.json
,它记录包依赖关系,可以在发布时无需附带其他package,使用时再安装。
npm init # 之后一直回车,使用默认选项
# 或者
npm init -y
使用过程中,对
node_module
文件夹或package.json
的修改都会产生package-lock.json
文件,也记录这依赖关系,但它是为了保证重新安装依赖时版本树的一致性而存在的,因为package.json
使用了语义版本规则。重新安装包(npm install
)时,会安装package-lock.json
指定的版本安装。晕=_=
当package-lock.json
不存在时,重新安装依赖包,会根据语义版本规则安装:即主版本号一致,尽量安装最高的版本。
3.3 npm install
安装package,分全局和局部安装,如:
# 全局
$ npm install uglify-js --global
# 局部
$ npm install uglify-js
# 根据配置文件安装所有局部依赖
$ npm install
# 安装局部依赖为开发依赖
$ npm install uglify-js --save-dev
# 安装指定版本package
$ npm install underscore@1.9.0
一般依赖包(node_modules)不会放入git仓库中,取出来后,使用npm install
会自动安装所有的依赖(包括开发依赖包),但依赖的开发依赖包不会被安装(即开发依赖包无传递性)
npm安装package分全局或局部安装,全局安装的package如果有可执行文件(如npm),它会被添加到环境变量中,因此可以直接执行(如npm install underscore
);局部安装且有可执行文件(位于node_modules/.bin/
目录),此时可用npx来执行,它会自动寻找,如npx cowsay "Hello"
安装的package的版本受
package-lock.json
影响
3.4 npm list
实现已安装依赖形参的关系树
#全局
$ npm list --global
#全局,但指定深度
$ npm list -g --depth=0
#局部依赖树
$ npm list
3.5 npm uninstall
卸载package
$ npm uninstall underscore
3.6 npm update
更新包,它会同时修改package.json和package-lock.json文件,单独修改package.json是无效的。
$ npm update underscore
安装package后,再次npm install 该package,貌似也能升级,达到同样的效果。
3.7 npm search
在npm仓库中搜索package
$ npm search mkdir
3.8 npm run
运行package.json中scripts字段的脚本(shell脚本),如
{
// ...
"scripts": {
"build": "node build.js"
}
}
此时可运行
$ npm run build
# equivalent to execution
$ node build.js
显示所有可运行命令
$ npm run
运行过程:开启新shell,并暂时将node_modules/.bin
添加到环境变量中,然后运行脚本。
默认值、hooks、缩写,略!
参考:https://www.tutorialdocs.com/article/npm-scripts-tutorial.html
3.9 npm link
一般情况下,全局package不能直接引入,需要npm link创建符号链接。或者直接指定绝对路径。
略
3.10 npx
执行局部package的可执行文件(位于./node_modules/.bin/
目录下),如
npx cowsay
3.11 npm
上面的命令都是通过npm执行的,这里介绍的是它的帮助命令,如:
# 查看npm的使用
npm help
# 查看install命令的使用
npm help install
3.12 缓存
安装过的package会被缓存起来。
参考
- Getting started with Node.js modules: require, exports, imports and beyond
- A Beginner’s Guide to npm — the Node Package Manager
- Node.js wiki
- npm-package.json
- Npm Scripts Tutorial In 10 Minutes
- Module Wrapper Function
- Difference between “module.exports” and “exports” in the CommonJs Module System
- Difference between a module and a package in Node?
- NodeJS require a global module/package
- The npx Node Package Runner
- How to use or execute a package installed using npm
- What’s the difference between dependencies, devDependencies and peerDependencies in npm package.json file?
- NPM5, What is the difference of package-lock.json with package.json?