手把手教你开发第一个命令行程序
最后更新于:2022-04-02 06:52:56
## 手把手教你开发第一个命令行程序
[TOC]
### 需求:
仿照`npm`命令,实现我们自己的命令行程序**`mynpm`**,包括`mynpm -V ,mynpm init ,mynpm install`这几个命令
### 简单需求分析:
* `mynpm -V `
打印版本
* `mynpm init `
1.仿照npm init提示用户输入各种信息
2.所有用户输入信息保存到mypackage.json的文件中
* `mynpm install `
调用`npm install`命令,根据`-save` ,`--save-dev `, `-g`这几个参数的不同使用不同的处理方法
* `--salve`:模块安装成功之后,把模块名称写入`mypackage.json`文件的`dependencies`字段中
* `--save-dev`:模块安装成功之后,把模块名称写入`mypackage.json`文件的`devDependencies`字段中
* `-g`:全局安装模块
### 需求实现
#### 创建工程
1. `mkdir mynpm`
1. `cd mynpm`
1. `npm init`
1. `创建入口文件index.js`
#### 第三方模块安装
实现上面的需求,需要用到的模块:
* 路径处理
* 文件系统处理
* 进程
* shelljs
* 命令行参数处理
* 命令行交互模块
使用`npm init`命令时,需要用户确认输入信息,这时,需要格式化输出`JSON`到命令行,可以使用第三方模块`jsonFormat`
除了路径处理(`path`)模块,其他模块均为第三方模块,需要先安装才能引用
`npm install --save json-format co co-prompt commander fs-extra shelljs `
#### 代码实现
* 模块引用
~~~
#!/usr/bin/env node
//模块引用
var path = require('path');
var co = require('co');
var prompt = require('co-prompt');
var program = require('commander');
var fs = require('fs-extra');
var shell = require('shelljs');
//格式化输出JSON对象
var jsonFormat = require('json-format');
~~~
* 子命令和参数定义
~~~
//定义版本号,由本模块package.json的version决定,使用-V或者--version打印
program
.version(require('./package.json').version)
.usage('[command] [options]')
.option('-d, --description', 'Description of the module.'); //参数定义
program
.command('init')
.description('general package.json')
.option('-y,--yes', 'general package.json without prompt')
.action(init);//init命令触发时,调用init()方法
program
.command('install [optional...]')
.description('install modules')
.option('-S,--save', 'save modules as dependencies')
.option('-D,--save-dev', 'save modules as dependencies')
.option('-g,--global', 'save modules as global')
.action(install);//install命令触发时,调用install()方法
program.parse(process.argv);
~~~
* 实现 init()方法
~~~
/**
* 处理init命令
* @param {Object} pOptions - init命令的参数
*/
function init(pOptions) {
co(function*() {
var mypackage = {};
var isPromptMode = pOptions.yes ? false : true;
//默认名称为当前文件夹名称的小写
var _defaultName = path.basename(process.cwd()).toLowerCase();
//默认入口文件为index.js
var _defaultEntryPoint = 'index.js'
// var jsArr = shell.ls('*.js');没有js文件时,有额外输出
var jsArr = shell.ls();
//当前工作目录下,如果存在js文件,将找到的第一个js文件作为默认值
for (var i = 0; i < jsArr.length; i++) {
if (path.extname(jsArr[i]) === '.js') {
_defaultEntryPoint = jsArr[i];
break;
}
};
var _testCommand = 'echo \"Error: no test specified\" && exit 1';
//拼接将生成的package.json文件的绝对路径
var mypackagePath = path.resolve(process.cwd(), 'package.json');
var _inputName = '',
_inputVersion = '',
_inputDesc = '',
_inputEntryPoint = '',
_inputTestCommand = '',
_gitRepository = '',
_keywords = '',
_author = '',
_license = '',
_confirm = '';
if (isPromptMode) {
console.log('This utility will walk you through creating a package.json file.It only covers the most common items, and tries to guess sensible defaults.\n\n' + 'Press ^C at any time to quit.');
_inputName = yield prompt('name: (' + _defaultName + ') ');
_inputVersion = yield prompt('version: (1.0.0) ');
_inputDesc = yield prompt.multiline('description: ');
_inputEntryPoint = yield prompt('entry point: (' + _defaultEntryPoint + ') ');
_inputTestCommand = yield prompt('test command: ');
_gitRepository = yield prompt('git repository: ');
_keywords = yield prompt('keywords: ');
_author = yield prompt('author: ');
_license = yield prompt('license: (ISC) ');
}
//处理将生成的package.json信息
mypackage.name = _inputName || _defaultName;
mypackage.version = _inputVersion || '1.0.0';
mypackage.description = _inputDesc || '';
mypackage.main = _inputEntryPoint || _defaultEntryPoint;
mypackage.scripts = {};
mypackage.scripts.test = _inputTestCommand || _testCommand;
if (_gitRepository) {
mypackage.repository = {};
mypackage.repository.type = 'git';
mypackage.repository.url = _gitRepository;
}
mypackage.keywords = _keywords.split(' ') || [];
mypackage.author = _author || '';
mypackage.license = _license || 'ISC';
if (isPromptMode) {
console.log('About to write to ' + mypackagePath + '\n');
console.log(jsonFormat(mypackage) + '\n');
_confirm = yield prompt.confirm('Is this ok?(y|n)');
if (!_confirm) {
console.log('Aborted');
process.exit();
}
} else {
console.log('Wrote to ' + mypackagePath + '\n');
console.log(jsonFormat(mypackage) + '\n');
}
//将信息写入package.json文件
fs.outputJsonSync(mypackagePath, mypackage);
/*确保入口文件存在——优化npm init方法,懒得每次都创建入口文件(比如index.js)*/
fs.ensureFileSync(mypackage.main);
process.exit();
});
}
~~~
* 实现install()方法
~~~
/**
* 处理install命令
* @param {Array} pOptional - 安装的模块名称
* @param {Object} pOptions - install命令后面带的参数
*/
function install(pOptional, pOptions) {
var _command = ['npm', 'install'];
_command = _command.concat(pOptional);
if (pOptions.global)
_command.push('-g')
else if (pOptions.save) {
ensurePackageJsonExist();
_command.push('--save')
} else if (pOptions.saveDev) {
ensurePackageJsonExist();
_command.push('--save-dev')
}
// console.log(_command.join(' '))
shell.exec(_command.join(' '), { async: true });
}
/**
* package.json不存在,显示帮助信息
*/
function ensurePackageJsonExist() {
if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {
console.log('在当前文件夹下找不到\"package.json\“文件,建议先init')
program.help();
}
}
~~~
### 使用命令行程序
#### 使用前的配置
为了能全局使用命令行程序,必须先在package.json文件中加一个字段
~~~
"bin": {
"mynpm": "./index.js"
},
~~~
这里,`mynpm`是我定义的名称(读者可自行定义),后面将使用`mynpm`来讲解命令的调用
>Tips:
要保证入口文件(`index.js`)第一行已经加上`#!/usr/bin/env node`,配置才算成功
#### 发布后再安装使用
* 发布模块
1. npm login
1. npm publish
* 使用已发布的模块**(假设发布模块的名称为`mynpm-cli`)**
1. 安装:
`npm install -g mynpm-cli`
1. 现在你可以使用mynpm命令代替npm命令来执行init和install了
#### 本地使用不发布
1. cd到模块工作目录下
1. 执行`npm link`或者`npm install -g`
1. 现在你可以使用mynpm命令代替npm命令来执行init和install了
### 完整demo源码下载地址
**mynpm-cli demo 源码下载地址:https://github.com/outsiderLazy/mynpm-cli
** **mynpm-cli模块npm地址:https://www.npmjs.com/package/mynpm-cli
** >Tips: 1.demo模块原本的名称是`mynpm`,但该名称已经被占用,故模块改名为`mynpm-cli`。 为了命令的简洁,`bin`的字段没改,模块安装成功之后,你可以直接使用`mynpm init`和`mynpm install`命令 2.如果你下载了源码,想要本地使用,需要先在工作目录下运行`npm install`安装依赖库
';
** **mynpm-cli模块npm地址:https://www.npmjs.com/package/mynpm-cli
** >Tips: 1.demo模块原本的名称是`mynpm`,但该名称已经被占用,故模块改名为`mynpm-cli`。 为了命令的简洁,`bin`的字段没改,模块安装成功之后,你可以直接使用`mynpm init`和`mynpm install`命令 2.如果你下载了源码,想要本地使用,需要先在工作目录下运行`npm install`安装依赖库
隐式调用node执行程序
最后更新于:2022-04-02 06:52:53
## 隐式调用node执行程序
前面我们在执行`node`程序的时候都是使用`node filePath`(比如,`node index`)的形式。但是,对于命令行程序,像`npm,git`,你在命令行使用这些命令行程序时,前面不会再带一个执行程序名称,而我们在[命令行参数处理](313192)这一小节使用自定义命令参数时,需要使用形如`node index exec`的命令,这个过程很繁琐,这一小节将介绍如何简化这个过程。
1.创建一个`node`工程(创建文件夹,将工作目录切换到文件加,`npm init`生成`package.json`,创建`index.js`文件)
2.打开`index.js`文件,输入下面内容:
~~~
#!/usr/bin/env node
console.log('Hello,world!');
~~~
我们在最前面加了`#!/usr/bin/env node`,它的作用是指定`index.js`的执行程序`node`,这时,你可以使用` ./index.js `直接执行`index.js`文件了。
3.虽然我们将第一个参数`node`去掉了,但是`index.js`这个文件名称太普遍了,并且,如果不在`index.js`的工作目录下,执行`index.js`的时候还要输入相对`index.js`的路径,这个过程很繁琐,我们需要更简单的方法。
下面介绍解决方案:
1)打开`package.json`文件,加入一个字段`bin`,如下:
~~~
"bin":{
"mymodule":"./index.js"
},
~~~
这里`mymodule`你可以自定义,如果是准备发布的模块,一般会使用模块名称,如果是本地使用,你开心就好。
package.json完整文件如下:
~~~
{
"name": "hello_module_outsider",
"version": "1.0.0",
"description": "My first node module",
"main": "index.js",
"bin":{
"mymodule":"./index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"hello",
"module"
],
"author": "outsider",
"license": "ISC"
}
~~~
2)在当前工作目录下执行`npm link`,把模块链接的本地`npm`仓库
至此,我们可以完全使用 `mymodule`来替代`node index.js`了,而且,这个它是全局的,你可以在任何目录下使用。
';
命令行交互
最后更新于:2022-04-02 06:52:51
## 命令行交互
在使用`npm init`的时候,你可以根据提示输入信息,命令行会根据你输入的信息做进一步的信息反馈,这就是一个命令行交互的过程。这一小节,我们将介绍第三方模块`co-prompt`,它能轻松实现命令行交互程序的开发。
[TOC]
### 安装
`co-prompt`模块依赖于`co`模块,所以,两个模块我们都需要安装。这里只介绍本地安装
>Tips :
`co`模块是非常有名的异步程序处理模块,这里不展开,建议自行学习。
学习`co`模块之前,需要先了解一下`ES6`的新特性[Generator 函数](http://es6.ruanyifeng.com/#docs/generator)
~~~
$ npm install co co-prompt --save
~~~
### co-prompt的使用
#### 引用
~~~
var co = require('co');
var prompt = require('co-prompt');
~~~
#### API
**普通文本提示**
* prompt(msg)
* `msg `:提示用户信息的具体内容
例子:
**密码文本提示,密码非明文显示**
* prompt.password(msg, [mask])
* `msg `:提示用户信息的具体内容
* `mask `:输入密码时的显示替换字符,默认是"*"
**多行文本提示**
* prompt.multiline(msg)
* `msg `:提示用户信息的具体内容
**确认提示信息**
* prompt.confirm(msg)
* `msg `:提示用户信息的具体内容
* `返回值 `:true|false
>Tips:
`confirm()`方法只有在用户输入`[y|yes|ok|true]`这4个值(不区分大小写)时,才返回`true`,其他情况都是`false`,所以,你的确认提示信息必须引导用户输入合适的值或者你程序的业务逻辑需要做必要调整
#### 综合例子
~~~
var co = require('co');
var prompt = require('co-prompt');
co(function*() {
var username = yield prompt('username: ');
var pwd = yield prompt.password('password: ');
var desc = yield prompt.multiline('description:');
var ok = yield prompt.confirm('are you sure?(yes|no)');
console.log('hello %s %s\n', username, pwd);
console.log('you describe yourself as:\n' + desc);
console.log('answer: %s', ok);
process.exit();
});
~~~
### co和co-prompt API地址:
**co 官网地址:https://www.npmjs.com/package/co
** **co-prompt 官网地址:https://www.npmjs.com/package/co-prompt
**
';
** **co-prompt 官网地址:https://www.npmjs.com/package/co-prompt
**
命令行参数处理
最后更新于:2022-04-02 06:52:49
## 命令行参数处理
在使用其他命令行程序的过程中(如`npm`),我们经常会使用`--help`命令来查询参数的用法,在输入错误的参数时,还会有提示信息。这些,都是我们在开发自己命令中需要考虑的。我们可以使用`process.argv`属性来获取用户从命令行输入的参数,然后,再自己处理各个参数。聪明如你,一定会想到这么普遍的用法肯定有第三方的模块可以简化处理过程。而这就是我们本章要介绍的模块——[commander](https://www.npmjs.com/package/commander)
[TOC]
### 安装
全局安装
~~~
$ npm install commander -g
~~~
本地安装
~~~
$ npm install commander --save
~~~
### commander的使用说明
`commander`的使用官网提供了几个常用的例子,不过,官网写得比较简洁,不适合新手阅读。这里,我将在例子上加上必要的注释,为了简洁,前面的例子已经注释说明的,后面的例子就不再赘述。
#### 参数解析(parse方法的使用)
下面这个例子的关注点放在`commander`的解析流程上,`commander`的使用流程如下:
1.使用`option()`方法自定义参数;
2.使用`parse()`方法解析用户从命令行输入的参数。
~~~
//index.js
//引用commander模块,这里返回的是一个commander对象
var program = require('commander');
program
.version('0.0.1')//定义版本号
.option('-p, --peppers', 'Add peppers')//参数定义
.option('-P, --pineapple', 'Add pineapple')
.option('-b, --bbq-sauce', 'Add bbq sauce')
.option('-c, --cheese [type]', 'Add the specified type of cheese [marble]', 'marble')
.parse(process.argv);//解析命令行参数,参数定义完成后才能调用
console.log('you ordered a pizza with:');
if (program.peppers) console.log(' - peppers');
if (program.pineapple) console.log(' - pineapple');
if (program.bbqSauce) console.log(' - bbq');
console.log(' - %s cheese', program.cheese);
~~~
`parse()`方法先对`option()`方法定义的参数进行赋值,然后将剩下的参数(未定义的参数)赋值给`commander`对象的`args`属性(`program.args`),`program.args`是一个数组。
>Tips:
链式写法
由于`version(),option()`方法的返回值还是`Commander对象`,所以,可以采用链式写法,即可以用"."来连接各个方法,就是上面例子的写法`program.version(...).option(...)`
在命令行运行上面的例子:
~~~
$ node index -p
~~~
打印结果为
~~~
$ you ordered a pizza with:
- peppers
- marble cheese
~~~
你在命令行运行 `node index --peppers`也会打印出相同的结果。
>Tips :
这里的`index`等价于`index.js`,例子存放文件的路径,只是把.js后缀省略了
>Note:
短标志可以作为单独的参数传递。像` -abc `等于` -a -b -c`。多词组成的选项,像“`--template-engine`”会变成` program.templateEngine` 等。
#### 强制多态(option()方法的使用)
**`option()`方法的定义**
* option(flags, description, fn, defaultValue)
* `flags ` : 自定义参数,格式为`"-shortFlag, --longFlag null||[value]|.."`
* -shortFlag:”-“后面跟的是自定义参数的短标志,一般用longFlag的第一个字母(区分大小写)
* --longFlag :“--”后面跟的是自定义参数的长标志,shortFlag和longFlag必须同时存在,
* `null||[value]`:有3种情况
* `null`——不用输入这一部分的内容,即只写前面两部分,这里为了说明,才写的null,该自定义参数将在程序中被解析为布尔值`(true|false)`
* ``——“<>”修饰,用户在使用-shortFlag(或--longFlag)时,强制后面带一个值,如果不输入该值,命令行程序会中断。该自定义参数将在程序中被解析为字符串(String)
* `[value]`——”[]“修饰,用户在使用-shortFlag(或--longFlag)时,允许后面带一个值,用户不输入使用默认值或undefined。
* `description ` : 对flags参数的描述
* `fn ` : 自定义处理参数的方法,如果传入的不是一个方法,会先判断是否为一个正则表达式,如果不是,则视为defaultValue(默认值),
* `defaultValue ` :自定义参数默认值
* `返回值
';
shelljs
最后更新于:2022-04-02 06:52:46
## [shelljs](https://www.npmjs.com/package/shelljs)
`shelljs`模块重新包装了`child_process`,调用系统命令更加简单。
`shelljs`是`Unix Shell`在`Node.js API`层的轻量级实现,可以支持`Windows、Linux、OS X`。你可以像在`Unix`命令行敲命令一样书写代码
[TOC]
### shelljs的安装
`shelljs`是第三方模块,需要安装后才能使用。
* 全局安装
~~~
$ npm install shelljs -g
~~~
* 本地安装
将当前工作目录切换为需要使用`shelljs`模块的目录(已经创建了`package.json`文件的目录)
~~~
$ npm install shelljs --save
~~~
### shelljs的使用说明
对于熟悉`Unix Shell`脚本的开发者,简单扫一眼API就能愉快的开始写代码了。不熟悉的也没关系,`shelljs`绝大部分命令都是对文件和文件夹的操作,我们在[文件系统处理](313189)一节已经详细的将对文件系统处理相关的接口介绍了一遍,使用`fs-extra`模块处理文件系统相关的操作会简单很多,
先看个例子:
~~~
var shell = require('shelljs');
//判定git命令是否可用
if (!shell.which('git')) {
//向命令行打印git命令不可用的提示信息
shell.echo('Sorry, this script requires git');
//退出当前进程
shell.exit(1);
}
//先删除'out/Release'目录
shell.rm('-rf', 'out/Release');
//拷贝文件到'out/Release'目录
shell.cp('-R', 'stuff/', 'out/Release');
//切换当前工作目录到'lib'
shell.cd('lib');
//shell.ls('*.js')返回值是一个包含所有js文件路径的数组
shell.ls('*.js').forEach(function(file) {//遍历数组
//sed命令用于文件内容的替换,这里是对每个文件都执行如下3步操作,更改版本信息
shell.sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
shell.sed('-i', /^.*REMOVE_THIS_LINE.*$/, '', file);
shell.sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, shell.cat('macro.js'), file);
});
//切换当前工作目录到上一层
shell.cd('..');
//同步执行git命令提交代码
if (shell.exec('git commit -am "Auto-commit"').code !== 0) {
shell.echo('Error: Git commit failed');
shell.exit(1);
}
~~~
上面的例子展示了一个可发布版本提交到`git`仓库的过程。
`shelljs`的方法都遵循:
**方法名就是我们常用的执行命令,而方法参数就是命令行参数。只是有些方法对命令行参数做了变形和拓展。**
### 重要方法介绍
#### exec()
* exec(command [, options] [, callback])
* `command `:要在命令行执行的完整命令
* `options
';
进程
最后更新于:2022-04-02 06:52:44
# 进程
本小节将介绍两方面内容——`process`(主进程)和`child_process`(子进程)。
[TOC]
## [process(主进程)](http://nodejs.cn/api/process.html)
>`process `对象是一个` global `(全局变量),提供有关信息,控制当前 `NodeJs `进程。作为一个对象,它对于` NodeJs`应用程序始终是可用的,故无需使用 `require()`。
### 常用属性和方法
这里会简单介绍`process`的常用属性和方法,对于`NodeJs`中文网已经做了详尽翻译的,这只是以超链接的方式提供,你可以直接点击跳转。
#### process.argv
**`process.argv`属性返回一个数组,该数组包含当`NodeJs`进程启动时,命令行传递到进程的参数。数组的第一个元素是*[process.execPath](http://nodejs.cn/api/process.html#process_process_execpath)*返回的结果(即执行`NodeJs`进程的执行程序的绝对路径名称,如`/usr/local/bin/node`),数组的第二个元素是被执行文件的名称,剩下的元素可以是任何命令行参数(用户可以自定义)**
* `process.argv`
* ``
例子:
~~~
//index.js
// 遍历并打印process.argv
process.argv.forEach(function(val, index) {
console.log(index + ':' + val)
});
~~~
上面的例子我们把代码写在`index.js`文件中,现在我们打开命令行,使用下面的命令启动`NodeJs`进程:
~~~
$ node index.js one two=three four
~~~
回车运行,命令行输出下面的结果:
~~~
0: /usr/local/bin/node
1: /Users/outsider/develop/node/process/index.js
2: one
3: two=three
4: four
~~~
其中,数组第一个元素`process.argv[0]=“/usr/local/bin/node”`,这是我电脑上`node`执行程序的路径,数组的第二个元素`process.argv[1]=“/Users/outsider/develop/node/process/index.js”`是`index.js`文件的路径
#### process.chdir(directory)
**将`NodeJs`进程当前工作目录切换为目标目录,和你在命令行使用`cd directory`一样效果,如果目标目录不存在,则抛出异常。**
* ` process.chdir(directory)`
* `directory `:目标目录路径
#### process.cwd()
**获取当前`NodeJs`进程所在的工作目录**
* `process.cwd()`
* `Returns: `:返回当前`NodeJs`进程所在的工作目录
#### process.exit([code])
**调用`process.exit()`方法可以强制结束当前`NodeJs`进程。**
* `process.exit([code])`
* `code `:进程退出码,默认值是0,即正常退出
>Note:
一般情况下,我们不会主动调用`process.exit()`,这是因为一旦调用`process.exit()`方法,`NodeJs`进程会以最快的速度响应退出进程的指令,即使现在`NodeJs`进程中还有一些异步程序没执行完成,也不会等它执行完。
在程序开发过程中,很多时候,我们需要表达程序是非正常退出的,这个时候我们向程序调用者输出各种标志信息。官网建议我们通过以下两种方式实现
1.直接设置`process.exitCode`(即进程退出码),然后返回。
2.抛出一个异常
#### process.pid
**每个进程启动的时候`NodeJs`都会分配为其一个唯一的进程ID。**
* ` process.pid`
* ` `:进程ID(整形数据)
>一种应用场景:
假如你用`NodeJs`写了一个服务程序,服务器启动之后,你又写了一个守护程序保活,如果服务器程序由于未知原因挂起了,并且过了预设时间仍然没有恢复,这时,你就可以强制关闭该进程,开启一个新的进程。这个过程,你需要先记住服务进程的`pid`,然后在需要的时候调用`process.kill(pid)`杀掉指定进程。
#### process.kill(pid[, signal])
**杀死指定`pid`的进程,如果该`pid`不存在,则抛出异常**
* ` process.kill(pid[, signal])`
* ` pid `:一个进程ID
* `signal | `:传递到指定`pid`进程的信号信息。默认值是”SIGTERM“
#### process.platform
**`process.platform`属性将返回系统的标识符**,比如:`'darwin', 'freebsd', 'linux', 'sunos' or 'win32'`,这个属性可以用于判断当前`NodeJs`进程运行在哪个系统上。
* ` process.platform`
* ` `:当前系统的标识符。
#### [process.stderr](http://nodejs.cn/api/process.html#process_process_stderr)
#### [process.stdin](http://nodejs.cn/api/process.html#process_process_stdin)
#### [process.stdout](http://nodejs.cn/api/process.html#process_process_stdout)
## [child_process(子进程)](http://nodejs.cn/api/child_process.html#child_process_child_process)
>`child_process `模块提供了衍生子进程的能力
`child_process `模块很重要,他是命令行脚本程序开发必不可少的一环,有了`child_process`你可以在主程序中,开启一个新的进程,执行其他程序任务。
`NodeJs`中文网提供了较为完整的`API`翻译,所以,这里不再赘述。
`child_process`需要重点关注以下两个方法:
1. [spawn](http://nodejs.cn/api/child_process.html#child_process_child_process_spawn_command_args_options)
2. [execFileSync](http://nodejs.cn/api/child_process.html#child_process_child_process_execfilesync_file_args_options)
>Tips:
`child_process`一章的阅读可以适当囫囵吞枣,下一小节,我们将介绍`shelljs`——`child_process`的一个封装模块,处理子进程相当简单。
## process和child_process API地址:
**process中文网地址:http://nodejs.cn/api/process.html**
**child_porcess中文网地址:http://nodejs.cn/api/child_process.html**
';
文件系统处理
最后更新于:2022-04-02 06:52:42
## 文件系统处理
我们在开发命令程序的时候,经常需要对文件做各种操作,比如,创建文件、删除文件、修改文件内容、遍历文件等。虽然`NodeJs`内置了`fs`模块供我们使用,但是其功能相对有限,这里,我将介绍一个第三方扩展模块[fs-extra](https://www.npmjs.com/package/fs-extra)。
[TOC]
### fs-extra模块与fs模块的关系
`fs-extra`模块是`fs`模块的扩充。也就是说,使用`fs-extra`模块可以做到以下两点:
1.直接调用`fs`模块所有方法(所以,你可以完全抛弃`fs`模块)
2.调用`fs-extra`扩充的方法
### fs-extra模块的安装
* 全局安装
~~~
$ npm install fs-extra -g
~~~
* 本地安装
将当前工作目录切换为需要使用`fs-extra`模块的目录(已经创建了`package.json`文件的目录)
~~~
$ npm install fs-extra --save
~~~
### 同步操作与异步操作
在介绍具体方法之前,有必要介绍一下文件系统的同步(`Sync`)方法和异步(`Async`)方法。
* 同步方法——只有等被调用方法执行结果返回之后,才会执行方法后面代码,执行过程是阻塞的。
* 异步方法——方法被调用后无需等待结果返回,直接执行后面代码,执行结果以回调的方式返回。
`fs-extra`的方法基本上都有一个同步和一个异步的方法,它们的方法定义也非常有规律,**异步方法只需在同步方法的基础上去掉名字的`Sync`,最后一个参数改为回调方法即可**,下面是一个例子:
~~~
var fse = require('fs-extra');
///异步方法
fse.emptyDir('mydir', function(err){
if (err) return console.error(err)
console.log('success!')
})
//同步方法
fse.emptyDirSync('mydir');
~~~
***为了文章的简介,下面介绍的常用方法,只介绍同步方法,不介绍异步方法,需要用到的时候,你可以根据规律自行补全。***
### fs-extra常用方法
#### copySync
**将源文件(或目录)拷贝到目标文件(或目录)**
* `copySync(src, dest, [options])`
* `src `: 源文件或目录路径
* `dest `: 目标文件或目录路径
* `options
';
路径处理
最后更新于:2022-04-02 06:52:40
## 路径处理
在开发命令行程序的时候,我们经常需要做路径的拼接、转换、相对路径和绝对路径互转,拆分路径等,这个时候,使用`NodeJs`的内置模块[path](http://nodejs.cn/api/path.html)就能轻松搞定这些问题。
[TOC]
### path的引用
~~~
var path = require('path');
~~~
### path常用方法
#### path.basename
* `path.basename(path[, ext])`
* `path `
* `ext 可选的文件扩展名`
* `返回: `
`path.basename() `方法返回一个` path `的最后一部分,类似于` Unix `中的 `basename `命令。
例子:
~~~
path.basename('/foo/bar/baz/asdf/quux.html')
// 返回: 'quux.html'
path.basename('/foo/bar/baz/asdf/quux.html', '.html')
// 返回: 'quux'
~~~
如果 path 不是一个字符串或提供了` ext `但不是一个字符串,则抛出 `TypeError`。
#### path.extname
* ` path.extname(path)`
* `path `
* `返回: `
`path.extname() `方法返回 `path `的扩展名,即从 `path `的最后一部分中的最后一个 .(句号)字符到字符串结束。 如果 `path `的最后一部分没有 . 或 `path` 的文件名(见 `path.basename()`)的第一个字符是 .,则返回一个空字符串。
~~~
path.extname('index.html')
// 返回: '.html'
path.extname('index.coffee.md')
// 返回: '.md'
path.extname('index.')
// 返回: '.'
path.extname('index')
// 返回: ''
path.extname('.index')
// 返回: ''
~~~
如果` path` 不是一个字符串,则抛出 `TypeError`。
#### path.join
* `path.join([...paths])`
* `...paths ` 一个路径片段的序列
* `返回: `
>`path.join() `方法使用平台特定的分隔符把全部给定的 `path `片段连接到一起,并规范化生成的路径。
>长度为零的 `path` 片段会被忽略。 如果连接后的路径字符串是一个长度为零的字符串,则返回 '.',表示当前工作目录。
例子:
~~~
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')
// 返回: '/foo/bar/baz/asdf'
path.join('foo', {}, 'bar')
// 抛出 TypeError: path.join 的参数必须为字符串
~~~
如果任一路径片段不是一个字符串,则抛出` TypeError`。
#### path.resolve
* `path.resolve([...paths])`
* `...paths 一个路径或路径片段的序列`
* `返回: `
>**`path.resolve() `方法会把一个路径或路径片段的序列解析为一个绝对路径。**
>给定的路径的序列是从右往左被处理的,后面每个` path `被依次解析,直到构造完成一个绝对路径。 例如,给定的路径片段的序列为:/`foo、/bar、baz`,则调用 `path.resolve('/foo', '/bar', 'baz') `会返回` /bar/baz`。
>**如果处理完全部给定的 `path` 片段后还未生成一个绝对路径,则当前工作目录会被用上。**
>生成的路径是规范化后的,且末尾的斜杠会被删除,除非路径被解析为根目录。
>长度为零的` path `片段会被忽略。
>**如果没有传入 `path `片段,则` path.resolve() `会返回当前工作目录的绝对路径。**
例子:
~~~
path.resolve('baz', 'js','test');//最常用
//如果当前工作目录为 /foo/bar
//则返回 ‘/foo/bar/baz/js/test
path.resolve('/foo/bar', './baz')
// 返回: '/foo/bar/baz'
path.resolve('/foo/bar', '/tmp/file/')
// 返回: '/tmp/file'
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif')
// 如果当前工作目录为 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
~~~
### path API地址
**Node.js中文网path的API:http://nodejs.cn/api/path.html**
';
常用模块介绍
最后更新于:2022-04-02 06:52:37
用NodeJs开发命令行程序
最后更新于:2022-04-02 06:52:35
## 用NodeJs开发命令行程序
本章将较为详尽的介绍`NodeJs`开发命令行程序的常用模块、隐式调用`node`执行程序以及一个比较完整的`demo`。只要你学习并理解了`npm`和`NodeJs`的入门教程(手把手系列),那么,通过,学习本章,你就可以完全掌握用`NodeJs`开发命令行程序的所有内容。
';
NodeJs资料整理
最后更新于:2022-04-02 06:52:33
##NodeJs资料整理 (持续更新!)
* [七天学会NodeJs](https://nqdeng.github.io/7-days-nodejs)
对于入门`NodeJs`而言,这是一个相当不错的教程。概念解释的比较透彻,用重点模块的实例讲解。
* [NodeJs中文网](http://nodejs.cn)
`NodeJs`中文网,可以下载`node`,查找`API`资料
* [NodeJs中文网API](http://nodejs.cn/api/)(常用)
API的翻译和官网发布的稳定版同步更新。生产环境下,绝大部分开发者用的是稳定版,所以,这个中文网相当有用的。
* [NodeJs英文官网](https://nodejs.org/en/)
目前,`NodeJs`中文网的资源相对比较有限,除了稳定版的一些资料,没有开发版的东西,所以,官网还是要知道的。
* [NodeJs英文官网API](https://nodejs.org/en/docs/) (常用)
`NodeJs API`资料最全的还是英文官网,如果你在中文网找不到自己需要的特定版本的API,可以上官网`API`找找。
';
手把手教你写Hello Node
最后更新于:2022-04-02 06:52:31
## 手把手教你写Hello Node
[TOC]
### 运行nodejs
1.打开命令行,输入`node`,然后回车,就进入命令交互模式了,可以输入一条代码语句后立即执行并显示结果,例如:
~~~
$ node
> console.log('Hello World!');
Hello World!
~~~
2.如果你需要写一大段代码(一段脚本、一个工程等),第一种运行的方式就不适合了。
这时,我们可以创建一个js文件,比如 `index.js`,然后,在`index.js`中输入要运行的代码,例如:
~~~
function hello() {
console.log('Hello World!');
}
hello();
~~~
代码写完后,打开命令行,`cd`到`index.js`所在工作目录,输入
~~~
$ node index.js
~~~
回车运行,结果如下:
~~~
$ node hello.js
Hello World!
~~~
这种运行方式遵循 `node path`(`path`为文件所在的路径,可以是绝对路径也可以是相对路径)的格式。所以,如果你懒得`cd`到`index.js`所在工作目录,你也可以输入相对路径或者绝对路径。
### 模块化
在前端开发过程中,我们习惯将代码合理的拆分在不同的`js`文件中,编写`NodeJs`也不例外,在`NodeJs`中,一个文件就是一个模块,每个模块都已经预定义好了3个变量——`require、exports、module`。
* require
>`require`函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。模块名可使用相对路径(以./开头),或者是绝对路径(以/或C:之类的盘符开头)。另外,模块名中的`.js`扩展名可以省略。
举个栗子:
~~~
//index.js
var foo1 = require('./foo');
var foo2 = require('./foo.js');
var foo3 = require('/home/user/foo');
var foo4 = require('/home/user/foo.js');
// foo1至foo4中保存的是同一个模块的导出对象。
~~~
另外,可以使用以下方式加载和使用一个`JSON`文件。
~~~
/赋值完成后,/变量data是一个json对象
var data = require('./data.json');
~~~
* exports
`exports`对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过`require`函数使用当前模块时,得到的就是当前模块的`exports`对象。以下例子中导出了一个公有方法。
~~~
//foo.js
//导出一个名为hello的方法
exports.hello = function () {
console.log('Hello World!');
};
~~~
这里,我们和`require`结合起来用,假设我里创建了一个`index.js`文件,他和`foo.js`在同级目录下,在`index.js`文件中引用`foo.js`
~~~
//index.js
//引用foo.js文件导出对象
var foo= require('./foo.js');
//执行hello
foo.hello();
~~~
代码编写完了,打开命令行,`cd`到`index.js`所在目录,运行结果如下:
~~~
$ node index.js
Hello World!
~~~
* module
通过`module`对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象。例如模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式。
~~~
module.exports = function () {
console.log('Hello World!');
};
~~~
以上代码中,模块默认导出对象被替换为一个函数。
实际开发过程中,特别是有一定代码量的项目,有一种做法,是把各个模块对象化,模块导出对象替换成自定义对象。下面举个例子:
~~~
//user.js
function User(pName){
this.name=pName;
this.say=function(){
console.log('你好,我是隔壁 '+this.name);
}
}
//将模块导出对象替换为User
module.exports=User;
~~~
~~~
//index.js
引用user.js导出模块
var User=require('./user.js');
//创建一个user对象
var user1=new User('老王');
//调用这个对象的say方法
user1.say();
~~~
你可以自行创建`index.js`和`user.js`这两个文件,将上面的代码拷贝到对应的文件,自己运行一下。这里,index.js是**模块的入口**,也就是这个工程的**主模块**
>Tip:模块初始化
一个模块中的J`S`代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。
### 使用NodeJs内置模块
`NodeJs`内置了很多常用模块,你可以在 [nodejs中文网](http://nodejs.cn/api/)上看到最新稳定版的API,下面举个栗子来说明如何使用`NodeJs`的内置模块。
~~~
//引用NodeJs内置模块文件系统模块fs
var fs=require('fs');
//在当前目录下创建一个名为“dir”的文件夹,如果已存在,则报错
fs.mkdirSync('dir');
~~~
`NodeJs`的内置模块直接通过模块名称引用即可
### 使用第三方模块
`NodeJs`只内置了我们常用的基本模块,实际开发过程中我们常常会用到第三方模块,这个时候`npm`就派上用场了。
这里,我们以第三方模块——文件系统拓展模块[fs-extra](https://www.npmjs.com/package/fs-extra)的使用为例。
1.初始化工程并生成`package.json`文件
~~~
$ npm init
~~~
2.安装第三方模块
~~~
$ npm install fs-extra --save
~~~
3.这个时候,你在当前工作目录下可以看到,多了一个文件夹`node_modules`,打开`node_modules`,多了一个`fs-extra`的文件夹,这就是我们从`npm`上下载的第三方模块`fs-extra`.我们可以开始使用了`fs-extra`模块了。
~~~
//index.js
//引用第三方模块fs-extra
var fse=require('fs-extra');
//创建一个名为dir的文件夹,如果已存在则不创建
fse.ensureDirSync('dir');
~~~
>Tips:
一个工程中,我们往往不只引入一个模块,只要`package.json`存在,你就可以使用`npm install`命令引入所有你需要的模块
';
NodeJs的版本管理
最后更新于:2022-04-02 06:52:29
## NodeJs的版本管理
[TOC]
### 为什么要做版本控制
`node`的版本发布遵循[语义化版本控制](http://www.tuicool.com/articles/JnmuE3R)的规则,这里简要描述一下我们需要关注的点
>版本号一共是3位
其格式为:MAJOR.MINOR.PATCH ,点号是分隔符,
MAJOR。进行了不兼容的API改动
MINOR。添加了向后兼容的新特性
PATCH。进行了向后兼容的bug修复
一般,下面3种情况,需要用到版本管理
1.`node`的版本现在有`0.x.x,4.x.x,5.x.x,6.x.x,7.x.x`,这几个大版本就是MAJOR这个版本号做了修改,说明它们的API不完全兼容,假如你下载的一个非常重要的第三方模块和你当前的开发环境出现node版本兼容性问题(比如,第三方模块用`6.x.x`,而你用的是`5.x.x`),你就需要用版本管理工具切换到第三方模块使用的版本。
2.你需要用到一个功能,但是,你电脑上安装的`node`环境太老了,不支持该功能,这时,你就需要更新`node`版本。
3.在当前`node`环境遇到某一个原生模块的bug并且这个bug新发布的模块已经解决了。
### 版本管理工具的使用
#### Windows系统
`Windows`系统下,这里推荐使用`nodist`,这里简单介绍下`nodist`的下载和使用
* [下载nodist](https://github.com/marcelklehr/nodist/releases);
* 下载完成之后,可视化安装`nodist`
* 安装完成之后就可以开始使用`nodist`做版本管理了。
下面将介绍`nodist`的使用,这里将介绍`nodist`的常用命令,有兴趣进一步了解`nodist`的朋友,可自行[查阅官网](https://github.com/marcelklehr/nodist)
* 打印`node`列表
~~~
$ nodist
#打印当前所有已安装到电脑所有node版本,高亮
~~~
~~~
$ nodist dist
#打印所有可下载的node版本
~~~
* 切换node版本
~~~
$ nodist global 4.x
#设置全局的node使用4.x版本(4.x为你想切换的版本号,下同)
#这个命令和你直接使用nodist 4.x效果一样,也是用得最多的命令
~~~
~~~
$ nodist local 4.x
#设置在当前文件目录下,node使用4.x版本,不影响全局环境变量
~~~
~~~
$ nodist env 4.x
#设置在当前命令行环境下,node使用4.x版本,不影响全局环境变量
~~~
* 切换npm版本
~~~
$ nodist npm global 3.x
#设置全局的npm使用4.x版本(4.x为你想切换的版本号,下同)
$ nodist npm global match
#开启nodist自动匹配模式,总是选择和当前环境node版本相匹配的npm版本
~~~
~~~
$ nodist npm local 2.x
#设置在当前文件目录下,npm使用2.x版本,不影响全局环境变量
~~~
~~~
$ nodist npm env 2.x
#设置在当前命令行环境下,npm使用4.x版本,不影响全局环境变量
~~~
* 其他操作
~~~
$ nodist + 4.x
# 先检查4.x版本是否存在,如果不存在则按安装
$ nodist + all
# 安装所有模块
~~~
~~~
$ nodist - 4.1.1
# 删除node 4.1.1版本
~~~
~~~
$ nodist --help
# 显示完整的nodist命令提示
~~~
#### OSX系统(Mac)
OSX系统下,我们使用`n`,这个模块非常简单,功能却非常强大
* 安装
~~~
$ sudo npm install n -g
#使用管理员权限全局安装,一般需要你输入管理员密码确认安装
~~~
` n`的使用
这里只介绍几个经常常用,更多细节请[查阅官网](https://github.com/tj/n)
* 打印`node`版本
~~~
$ n
#打印所有已下载的node版本,并高亮当前使用的node版本
~~~
~~~
$ n ls
#打印所有可下载的node版本,并高亮所有已下载版本,标记当前使用版本
~~~
~~~
$ n --latest
#打印最新发布的node版本号
~~~
~~~
$ n --stable
#打印最新发布的node稳定版版本号
~~~
* 切换node版本(使用管理员权限切换到相应的模块)
~~~
$ sudo n latest
#安装并切换或直接切换到最新发布的node版本
~~~
~~~
$ sudo n stable
#安装并切换或直接切换到最新发布的node 稳定版本
~~~
~~~
$ sudo n
#安装指定node版本
~~~
';
什么是NodeJs
最后更新于:2022-04-02 06:52:26
## 什么是nodejs
### nodejs的定义
引用nodejs中文网的定义:
>`Node.js `是一个基于 `Chrome V8` 引擎的 `JavaScript `运行环境。
`Node.js` 使用了一个事件驱动、非阻塞式 `I/O `的模型,使其轻量又高效。
`Node.js `的包管理器 `npm`,是全球最大的开源库生态系统。
引用[七天学会NodeJS](https://nqdeng.github.io/7-days-nodejs/#1.1)对node.js的解释
>`JS`是脚本语言,脚本语言都需要一个解析器才能运行。对于写在`HTML`页面里的`JS`,浏览器充当了解析器的角色。而对于需要独立运行的`JS`,`NodeJS`就是一个解析器。
>每一种解析器都是一个运行环境,不但允许`JS`定义各种数据结构,进行各种计算,还允许`JS`使用运行环境提供的内置对象和方法做一些事情。例如运行在浏览器中的JS的用途是操作`DOM`,浏览器就提供了`document`之类的内置对象。而运行在`NodeJS`中的`JS`的用途是操作磁盘文件或搭建`HTTP`服务器,`NodeJS`就相应提供了`fs`、`http`等内置对象。
### nodejs有什么用
>尽管存在一听说可以直接运行JS文件就觉得很酷的同学,但大多数同学在接触新东西时首先关心的是有啥用处,以及能带来啥价值。
>`NodeJS`的作者说,他创造`NodeJS`的目的是为了实现高性能`Web`服务器,他首先看重的是事件机制和异步IO模型的优越性,而不是`JS`。但是他需要选择一种编程语言实现他的想法,这种编程语言不能自带IO功能,并且需要能良好支持事件机制。JS没有自带IO功能,天生就用于处理浏览器中的`DOM`事件,并且拥有一大群程序员,因此就成为了天然的选择。
>如他所愿,`NodeJS`在服务端活跃起来,出现了大批基于`NodeJS`的`Web`服务。而另一方面,`NodeJS`让前端众如获神器,终于可以让自己的能力覆盖范围跳出浏览器窗口,更大批的前端工具如雨后春笋。
>因此,对于前端而言,虽然不是人人都要拿`NodeJS`写一个服务器程序,但简单可至使用命令交互模式调试`JS`代码片段,复杂可至编写工具提升工作效率。
>`NodeJS`生态圈正欣欣向荣。
';
NodeJs的使用
最后更新于:2022-04-02 06:52:24
## NodeJS的使用
本章将为你介绍什么是`NodeJs`、`NodeJs`的版本管理,以及`NodeJs`的基础知识入门。
因为`NodeJS`的中文教程和资料都比较多,所以,除了入门基础知识,其他内容都以推荐资料阅读的方式出现。
';
npm资料整理
最后更新于:2022-04-02 06:52:22
## npm资料整理 (持续更新)
* [npm 官网 API 地址](https://docs.npmjs.com/cli/init )
npm官网提供的API是纯英文的,推荐英文好的朋友可以直接看 * [npm模块管理器](http://javascript.ruanyifeng.com/nodejs/npm.html#toc18) ”npm模块管理器“介绍了大部分常用的API的使用,可以作为参考。 * [npm是干什么的?](https://zhuanlan.zhihu.com/p/24357770) “npm是干什么的?”介绍了npm的作用以及发展历程,对理解npm有好处,值得一看
';
npm官网提供的API是纯英文的,推荐英文好的朋友可以直接看 * [npm模块管理器](http://javascript.ruanyifeng.com/nodejs/npm.html#toc18) ”npm模块管理器“介绍了大部分常用的API的使用,可以作为参考。 * [npm是干什么的?](https://zhuanlan.zhihu.com/p/24357770) “npm是干什么的?”介绍了npm的作用以及发展历程,对理解npm有好处,值得一看
实用小技巧
最后更新于:2022-04-02 06:52:20
## 实用小技巧
1.如何解决npm下载模块慢的问题?
有的时候,在下载npm的一些模块的时,你发现进度条压根就不会动,速度慢得你想砸电脑。这里提供一种使用淘宝镜像的方法来解决这个痛点。
**使用`npm config`命令设置淘宝源地址:** ~~~ $ npm config set registry=https://registry.npm.taobao.org ~~~ 2.如何解决使用npm发布模块报错的问题? 1).你发布的模块重名了。由于你发布的模块和其他已经发布的模块重名,所以,你必须换一个模块名称。 发布模块的时候报错,一般有以下3种情况 2).你使用了npm的镜像地址。使用淘宝镜像(或其他镜像)是无法完成的,这时,你必须将registry的地址设置成npm官网的地址 **使用`npm config`命令设置`npm`官网源地址:** ~~~ $ npm config set registry=http://registry.npmjs.org ~~~ 3).网络问题,`npm`官网被和谐了,你需要使用梯子,这是程序猿的必备技能,不赘述。
';
**使用`npm config`命令设置淘宝源地址:** ~~~ $ npm config set registry=https://registry.npm.taobao.org ~~~ 2.如何解决使用npm发布模块报错的问题? 1).你发布的模块重名了。由于你发布的模块和其他已经发布的模块重名,所以,你必须换一个模块名称。 发布模块的时候报错,一般有以下3种情况 2).你使用了npm的镜像地址。使用淘宝镜像(或其他镜像)是无法完成的,这时,你必须将registry的地址设置成npm官网的地址 **使用`npm config`命令设置`npm`官网源地址:** ~~~ $ npm config set registry=http://registry.npmjs.org ~~~ 3).网络问题,`npm`官网被和谐了,你需要使用梯子,这是程序猿的必备技能,不赘述。
手把手教你创建发布第一个模块
最后更新于:2022-04-02 06:52:17
## 手把手教你创建发布第一个模块
[TOC]
### 准备工作:
从[node官网](http://nodejs.cn/download/)或者[node中文官网](https://nodejs.org/en/download/)下载`node`,并安装
`node`自带`npm`,所以,你不用再单独安装`npm`。
### 演示电脑环境说明:
~~~
电脑系统: OSX 10.11.6 和 Win7 64位系统
node版本: v6.5.0
npm版本: v3.10.3
~~~
### 模块的创建
1.打开命令行
2.切换工作目录到你的开发目录(或者其他任何能你开心的目录)
~~~
$ cd myPath
~~~
3.创建一个文件夹`helloModule`(Tip:文件名你完全可以自定义)
~~~
$ mkdir helloModule
~~~
4.切换工作目录到`helloModule`
~~~
$ cd helloModule
~~~
5.初始化模块,根据提示输入相应的模块信息
~~~
$ npm init
~~~
信息填写完成之后,会生成一个`package.json`文件,下面是我生成的`package.json`的内容:
~~~
{
"name": "hello_module_outsider",
"version": "1.0.0",
"description": "My first node module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"hello",
"module"
],
"author": "outsider",
"license": "ISC"
}
~~~
>Note:
这里有个问题需要特别注意,由于模块发布的时候,要保证模块名称的唯一性,所以,这里,我模块的名称我加了个个人标签`outsider`,保证这个模块的名称不会和`npm`上面已经发布的模块重名。建议你在创建模块的时候,加上专属于自己的标签,比如,你叫王老明,你的模块名称可以命名为`hello_module_wlm`,用拼音缩写作为标签。
6.创建模块入口文件"index.js"
7.打开"index.js",编写模块内容,这里,我们随便写一行代码
~~~
//向控制台输出“Hello,Module!”
$ console.log('Hello,Module!');
~~~
至此,一个简单可发布到`npm`的模块就创建完成了。
### 模块的使用
前面我们已经创建了一个名为`hello_module_outsider`的模块,本小节将以这个模块为例,来说明如何使用一个模块。
#### 发布模块
1.要发布一个模块,你必须先在[npm官网注册](https://www.npmjs.com/)一个npm账号。
2.打开命令行,将工作目录切换到`hello_module_outsider`模块的根目录下(如果你已经在`hello_module_outsider`的根目录下,则无需切换,Tip:包含`package.json`文件的目录就是模块的根目录,这里,你只需要切换到`helloModule`文件夹下即可)
2.登录npm账号
`$ npm login`
根据提示一次输入正确的npm账号和密码,
3.登录成功后,就可以发布`hello_module_outsider`模块了
`$ npm publish `
如果发布成功,命令行会返回类似下面的语句
~~~
$ + hello_module_outsider@1.0.0
~~~
命令行返回的结果表示——你已经在npm上发布了一个名为` hello_module_outsider`的模块,这个模块的版本号是`1.0.0`。
>Tips:
返回结果的格式是:`module_name@version`(即模块名称@版本号),
1.如果你发布的模块的名称是`hello_module_wlm`,那返回的结果就应该是`hello_module_wlm@1.0.0`,
2.这里的版本号也由你在`package.json`文件中定义的`version`字段决定的。一般,第一个,发布的版本定为`1.0.0`,以后,每次更新一个版本,版本号应做相应的叠加,版本号定义原则,请自行查阅。
OK,你的第一个`node`模块发布成功了!有没有点小激动?现在,你可以在`npm`官网搜索到你发布的模块了,其他开发者也可以搜索到你发布的模块了,赶快上npm官网搜索一下吧!
>Tips:
强烈建议在`package.json`中设置`keyword`关键字,这能提升你发布的模块被其他开发者搜索到的概率。
#### 不发布模块,本地使用
考虑一个场景,你开发了一个`node`模块,但是,由于种种原因(比如涉及公司保密信息或者单纯是因为网络原因),这个模块只能在自己的电脑上或者公司内部使用,不能发布,那么,这个时候,你就需要用到`npm link`命令了(具体用法请查看[npm常用命令](npm常用命令.md)中关于`npm link`的描述)
';
npm常用指令
最后更新于:2022-04-02 06:52:15
## npm常用指令
这一小节将介绍我们平时最常用到的几个指令。
[TOC]
### npm init
#### 命令
`$ npm init [-f|--force|-y|--yes]`
`npm init`用来初始化生成一个新的`package.json`文件。它会向用户提问一系列问题,如果你觉得不用修改默认配置,一路回车就可以了。
如果使用了`-f`(代表`force`)、`-y`(代表`yes`),则跳过提问阶段,直接生成一个新的`package.json`文件。
>Tips:
对package.json没有概念的朋友,请查阅[package.json的使用](313176)这一小节
* * * * *
### npm install
#### 命令
~~~
$ npm install (with no args, in package dir)
$ npm install [<@scope>/]
$ npm install [<@scope>/]@
$ npm install [<@scope>/]@
$ npm install [<@scope>/]@
$ npm install
$ npm install
$ npm install
alias: npm i
common options: [-S|--save|-D|--save-dev|-O|--save-optional] [-E|--save-exact] [-B|--save-bundle] [--dry-run]
~~~
>`npm install `这个命令官网的[API](https://docs.npmjs.com/cli/install)写了很多,这里只介绍常用的
* `$ npm install (在模块根目录下,不带参数)`
将模块的所有第三方依赖模块安装到根目录下的 node_modules 文件夹中, * `$ npm install -g(在模块根目录下)`
将当前模块全局安装,这样,你就可以在未发布模块的情况下,在自己的电脑上全局使用当前模块(即在命令行,无论处于哪个目录下,都可以引用当前模块,就像你使用npm命令一样) >Tips: 上面提到的两种安装方式,都会将package.json中,在dependencies和devDependencies这两个key下罗列的所有模块都进行安装,如果npm install这个命令后面加上参数 --production(或者将NODE_ENV这个环境变量设为 production),npm将只安装罗列在dependencies这个key下的模块 * `$ npm install`
安装指定路径下的模块,该路径的根目录下应该包含package.json文件。 * `$ npm install [<@scope>/] [-S|--save|-D|--save-dev|-O|--save-optional]`
>Tips: scope是模块的作用域 ,可选参数,一般只在发布私有模块的时候使用,绝大部分情况下,你用不到,这里可以先忽略。有兴趣可参考[npm 私有模块的使用](http://www.cnblogs.com/kelsen/p/4964574.html) 大多数场景下,我们会使用这个这个命令来安装最新发布在npm上的指定``的模块。
以sax模块为例:
`$ npm install sax`
这个命令就是从npm上拉取最新发布的sax模块,这个命令等价于
`$ npm install sax@latest`
本质上 ,执行的命令是:
* `$ npm install [<@scope>/]@ `
tag是在模块发布的时候给模块打的一个标签,其他开发者可以通过@不同的tag来安装打了相应tag的版本
* `npm install `命令根据安装环境的不同,有3个可选的参数可以加,这几个参数将在`package.json`中相应的`key`下面保存要安装的模块
* ` -S, --save`: 这个模块将作为`dependencies`的成员。
* ` -D, --save-dev`: 这个模块将作为`devDependencies`的成员。
* `-O, --save-optional`: 这个模块将作为`optionalDependencies`的成员
* ` $ npm install [<@scope>/]@`
``和``类似,都是在开发者发布模块时打上的标签,其他开发者可以通过这个标签指定安装某一个版本的模块
* `$ npm install `
>Tips:
tarball是Linux下最方便的打包命令
安装当前文件系统中指定路径的文件
例子:
~~~
$ npm install ./package.tgz
~~~
* `$ npm install `
从指定的URL地址下载并安装模块
例子:
~~~
$ npm install https://github.com/indexzero/forever/tarball/v0.5.6
~~~
* * * * *
### npm uninstall
#### 命令
~~~
$ npm uninstall [<@scope>/][@]... [-S|--save|-D|--save-dev|-O|--save-optional]
aliases: remove, rm, r, un, unlink
~~~
与`npm install `相反,不再赘述
* * * * *
### npm update
#### 命令
~~~
#升级当前项目的指定模块(在项目根目录下使用)
$ npm update [package name]
#升级全局安装的模块
$ npm update -g [package name]
~~~
`npm update`命令会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。
使用-`S`或`--save`参数,可以在安装的时候更新`package.json`里面模块的版本号。
>注意,从npm v2.6.1 开始,npm update只更新顶层模块,而不更新依赖的模块,以前版本是递归更新的。如果想取到老版本的效果,要使用下面的命令:
>`$ npm --depth 9999 update`
* * * * *
### npm link
#### 命令
~~~
$ npm link (in package dir)
$ npm link [<@scope>/][@]
alias: npm ln
~~~
我们假设这样一个场景,你刚开发完一个模块A(模块的名称暂定为aaa),你准备做发布前的最后测试。
一种实现的方式是:
1. cd到模块A的根目录下,执行 `npm install -g`,模块A就全局的安装到了你电脑上。
2. 切换一个新的目录,使用`npm init`构建一个新的工程B,这时,你就可以用IDE打开index.js,通过require('aaa')来使用模块A了。
上面的实现方式有一个问题,当你在测试过程中发现模块A有bug或接口不合理的地方,你需要在模块A的源码上做对应的修改,但是,这个改动只有重新执行`npm install -g`之后才会生效(模块A的修改无法实时的更新到全局)。这就稍显麻烦了,是否有办法让模块A的改动及时反馈的全局呢?有的!
我们来介绍满足这个需求的另外一种实现方式:
1. cd到模块A的根目录下,执行`npm link`,这时,在全局的npm模块仓库(node_modules)中,会创建一个指向模块A根目录的符号链接(symlink)
2. 切换到工程B,执行` npm link aaa`(Tip:aaa是模块A的名称,不是根目录的名称),这时,在工程B的根目录的node_modules文件夹下面会引入模块A。
使用`npm link`就能实现模块A的修改实时生效。
>Tips:
当你不再需要在工程里面使用“aaa”模块时,应该使用`npm unlink aaa` 删除符号链接
* * * * *
### npm login
登录npm账号,你只要在[npm官网](https://www.npmjs.com)注册一个账号,然后根据提示输入用户名和密码登录即可,只有登录了npm的账号才能发布npm模块。
* * * * *
### npm publish
#### 命令
~~~
$ npm publish [|] [--tag ] [--access ]
Publishes '.' if no argument supplied
Sets tag 'latest' if no --tag specified
~~~
`npm publish`用于发布你自己开发的模块。
最常用的场景是:
1. 使用`npm login`登录npm账号;
2. cd到要发布模块的根目录下(包含`package.json`的文件夹);
3. 执行`npm publish`。
这样,一个模块就发布到npm上去了,你可以登录[npm官网](https://www.npmjs.com)去查看你发布的模块。
> 上面提到的这种场景,会自动为你发布的模块打上两个标签,一个是版本号,由`package.json`文件的`version`字段决定,用于版本管理;另一个是`latest`标签,npm publish后面的可选参数 `--tag ` 的默认值就是**`latest`**,`tag`可以理解为辨识度更高的版本标记。
Note:
如果你npm设置了其他镜像作为源地址,在版本发布的时候要先设置为官网的地址!
`$ npm config set registry=http://registry.npmjs.org`
* * * * *
### npm unpublish
#### 命令
~~~
$ npm unpublish [<@scope>/][@]
~~~
当你发布的某一个版本的module有较大的问题,你就可以用 npm unpublish这个命令的删除之前发布的版本。
npm官网不建议使用该命令来取消某一个版本的发布,并且你只能在发布该版本的24小时之内取消发布,超过了这个24小时,你需要联系npm的技术支持( support@npmjs.com)来解决。
';
将模块的所有第三方依赖模块安装到根目录下的 node_modules 文件夹中, * `$ npm install -g(在模块根目录下)`
将当前模块全局安装,这样,你就可以在未发布模块的情况下,在自己的电脑上全局使用当前模块(即在命令行,无论处于哪个目录下,都可以引用当前模块,就像你使用npm命令一样) >Tips: 上面提到的两种安装方式,都会将package.json中,在dependencies和devDependencies这两个key下罗列的所有模块都进行安装,如果npm install这个命令后面加上参数 --production(或者将NODE_ENV这个环境变量设为 production),npm将只安装罗列在dependencies这个key下的模块 * `$ npm install
安装指定路径下的模块,该路径的根目录下应该包含package.json文件。 * `$ npm install [<@scope>/]
>Tips: scope是模块的作用域 ,可选参数,一般只在发布私有模块的时候使用,绝大部分情况下,你用不到,这里可以先忽略。有兴趣可参考[npm 私有模块的使用](http://www.cnblogs.com/kelsen/p/4964574.html) 大多数场景下,我们会使用这个这个命令来安装最新发布在npm上的指定`
package.json的使用
最后更新于:2022-04-02 06:52:13
## 【译】package.json的使用
> Tips:package.json这一章比较重要,所以,我对npm官方文档[Using a package.json](https://docs.npmjs.com/getting-started/using-a-package.json) 这一小节在个人理解的基础上做了翻译,你也可以直接查看[官方文档](https://docs.npmjs.com/getting-started/using-a-package.json)。
[TOC]
管理本地安装的包的最好方法是创建一个`package.json`文件。
**`package.json`文件会给你提供很多好东西:**
1. 它是你这个工程的基础依赖和基本信息描述的集合。
2. 它允许你使用语义化版本管理规则,指定项目中能使用的包的版本。
3. 使你的构建版本可以重新生成,方便你与其他开发者分享代码。
### 必要项
`package.json `必须包括以下几项:
* **"name"**
* 全部为小写字母
* 一个单词,无空格
* 允许半角破折号和下划线
* **"version"**
* 格式为 x.x.x
* 遵循语义化版本号规则 [semver spec](https://docs.npmjs.com/getting-started/semantic-versioning)
示例:
~~~
{
"name": "my-awesome-package",
"version": "1.0.0"
}
~~~
只要有这两项,就可以创建一个最简单的node 模块
### 创建package.json文件
`$ npm init [-f|--force|-y|--yes]`
npm init是初始化项目命令,会在你运行此命令的文件夹根目录下创建项目配置文件`package.json`。同时,在命令行,程序会依次提示你输入。这些问题最终会记录到`package.json`文件中。
当你在命令行输入`npm init`命令时,它会向用户提问一系列问题,如果不用修改默认配置,一路回车就可以了,这些问题最终会记录到`package.json`文件中。
在命令行回答问题这个过程不是必须的,你可以在后面添加 `-f`(即在命令行输入 `npm init -f`)跳过回答问题的过程,这时,程序会使用默认值直接生成`package.json`文件。`npm init` 有4个可选参数,这里,其他3个参数(`--force、-y、--yes`)的效果和 `-f`是一样的。
生成的`package.json`如下(npm版本不同,`JSON`字段会有不同):
~~~
{
"name": "my_package",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"repository": {
"type": "git",
"url": ""
},
"bugs": {
"url": ""
},
"homepage": ""
}
~~~
* name:模块的名称,默认值是作者名字(如果有设置),除非在git目录中,它会是git仓库的名字,否则,为当前所在文件夹的名称,当你将这个模块发布之后,其他开发者就可以通过搜索这个名称找到你发布的模块。
* version:版本号,刚初始化的项目总是1.0.0;
* description: 模块描述,默认为空,当你发布模块时,npm将使用README.md 或者README的第一行来描述,这个描述可以方便其他开发者找到你的模块,提高模块被其他开发者发现的概率。
* main:默认为index.js;
* scripts:默认创建一行空的测试脚本;(这里可以先适当囫囵吞枣)
* keywords:搜索关键字,默认为空,版本发布的时候,建议写上,方便其他开发者搜索
* author:作者
* license:默认为ISC开源证书
* repository: 如果你有把这个模块托管到git仓库(比如github),这里可以填写这个URL地址
* bugs: bug反馈地址(也是针对像在github做了代码托管的模块),默认为空
* homepage: 作者主页默认为空
你也可以通过set命令来设置npm init过程的默认值。比如下边的这些:
> npm set init.author.email "wombat@npmjs.com"
npm set init.author.name "ag_dubs"
npm set init.license "MIT"
### 自定义npm init过程
除了设置`npm init`的默认值,你还可以完全自定义`npm Init` 整个过程。只需要在用户目录下,创建一个`.npm-init.js`文件,然后在`.npm-init.js`中编写代码即可。你可以像下面这样编写`.npm-init.js`
~~~
module.exports = {
customField: 'Custom Field',
otherCustomField: 'This field is really cool'
}
~~~
你也可以自定义在`npm init`过程中命令行提问的问题,这时,你需要用到prompt方法,写法如下:
```
module.exports = prompt("what's your favorite flavor of ice cream buddy?", "I LIKE THEM ALL");
```
更多自定义npm init过程的细节可以参考[init-package-json](https://github.com/npm/init-package-json)
### 指定依赖包
你开发的模块很多时候需要引入其他开发者开发的第三方模块,这个时候,你需要在`package.json`中输入引入所有需要的模块,你可以通过以下两种方式来实现:
* dependencies: 这些模块是你开发的模块的依赖项,没有这些模块你的代码就会报错
* devDependencies: 这些模块只在开发环境下使用,比如`jshint、grunt`等开发过程中的辅助工具
你可以直接用IDE打开package.json文件,然后手动编辑,其格式如下:
~~~
{
"name": "my_package",
"version": "1.0.0",
"dependencies": {
"my_dep": "^1.0.0"
},
"devDependencies" : {
"my_test_framework": "^3.1.0"
}
}
~~~
不过,我们一般不会通过手动编辑`package.json`来引入第三方模块,而是通过`--save 和 --save-dev`这两个安装标记来实现,这是添加依赖到你的package.json文件的更简单(也更酷)的方式。
添加`package.json`依赖的入口(即执行该命令之后,第三方模块会被写入到`dependencies`字段中):
`$ npm install --save`
添加package.json开发环境依赖的入口(即执行该命令之后,第三方模块会被写入到`devDependencies`字段中):
`$ npm install --save-dev`
dependencies和devDependencies的成员都必须按照`“模块名称”:"模块版本"`来编写,
`模块版本`的写法遵从语义化版本管理的规则。
### 管理依赖包的版本
`npm`使用语义化版本管理依赖包,也就是我们常说的“SemVer”。
如果在项目文件夹下存在`package.json`文件,你在此文件夹下运行命令`npm install`,npm就会检查文件中列出的依赖包,并下载所有满足语义化规则的最新版本的依赖包。
语以化版本号的内容不是本教程的重点,请参考[语义化版本控制](http://www.tuicool.com/articles/JnmuE3R)
';