文章目录
文件操作
fs-extra
fs-extra是fs的一个扩展,提供了非常多的便利API,并且继承了fs所有方法和为fs方法添加了promise的支持。
npm install --save-dev fs-extra
应该总是fs-extra代替fs使用,所有fs方法都附在fs-extra,fs如果未传递回调,则所有方法都将返回promise。
大多数方法默认为异步,如果未传递回调,则所有异步方法将返回一个promise。
const fs = require('fs-extra')
// 异步方法,返回promise
fs.copy('/tmp/myfile', '/tmp/mynewfile')
.then(() => console.log('success!'))
.catch(err => console.error(err))
// 异步方法,回调函数
fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
if (err) return console.error(err)
console.log('success!')
})
// 同步方法,注意必须使用try catch包裹着才能捕获错误
try {
fs.copySync('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
// Async/Await:
async function copyFiles () {
try {
await fs.copy('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
}
copyFiles()
readdirp
fs.readdir 的递归版本,对外暴露 stream api
var readdirp = require('readdirp'),
path = require('path'),
es = require('event-stream');
// print out all JavaScript files along with their size
var stream = readdirp({ root: path.join(__dirname), fileFilter: '*.js' });
stream
.on('warn', function (err) {
console.error('non-fatal error', err);
// optionally call stream.destroy() here in order to abort and cause 'close' to be emitted
})
.on('error', function (err) {
console.error('fatal error', err);
})
.pipe(
es.mapSync(function (entry) {
return { path: entry.path, size: entry.stat.size };
})
)
.pipe(es.stringify())
.pipe(process.stdout);
filefilter 函数过滤
fileFilter(entry) {
return /\.html?$/.test(entry.basename)
}
entry 结果
parentDir : 'test/bed/root_dir1',
fullParentDir : '/User/dev/readdirp/test/bed/root_dir1',
name : 'root_dir1_subdir1',
path : 'test/bed/root_dir1/root_dir1_subdir1',
fullPath : '/User/dev/readdirp/test/bed/root_dir1/root_dir1_subdir1',
stat : [ ... ]
event-stream
Streams是 node 最好的也是最容易被误解的流处理工具,EventStream 是一个工具包,可以让创建和使用流变得容易。
所有event-stream
函数都返回Stream
.
//pretty.js
if (!module.parent) {
var es = require('event-stream');
var inspect = require('util').inspect;
process.stdin //connect streams together with `pipe`
.pipe(es.split()) //split stream to break on newlines
.pipe(
es.map(function (data, cb) {
//turn this async function into a stream
cb(null, inspect(JSON.parse(data))); //render it nicely
})
)
.pipe(process.stdout); // pipe it to stdout !
}
监听文件变化 chokidar
chokidar 最小化和高效的跨平台文件监听库
const chokidar = require('chokidar');
// One-liner for current directory
chokidar.watch('.').on('all', (event, path) => {
console.log(event, path);
});
文件匹配
glob
npm i glob
var glob = require("glob")
// options is optional
glob("**/*.js", options, function (er, files) {
// files is an array of filenames.
// If the `nonull` option is set, and nothing
// was found, then files is ["**/*.js"]
// er is an error object or null.
})
远程下载模板代码
download-git-repo
Download and extract a git repository (GitHub, GitLab, Bitbucket) from node.
$ npm install download-git-repo
download('gitlab:mygitlab.com:flippidippi/download-git-repo-fixture#my-branch', 'test/tmp', { headers: { 'PRIVATE-TOKEN': '1234' } } function (err) {
console.log(err ? 'Error' : 'Success')
})
也可以直接使用 git clone
的方式
命令行参数解析
minimist轻量级的命令行参数解析引擎
node.js的命令行参数解析工具有很多,比如:argparse、optimist、yars、commander。optimist和yargs内部使用的解析引擎正是minimist,如果你喜欢轻量级的技术,那么minimist足够简单好用,代码量也很少(只有几百行),非常适合研读。
minimist的特性比较全面:
- short options
- long options
- Boolean 和 Number类型的自动转化
- option alias
// test.js
var args = require('minimist')(process.argv.slice(2));
console.log(args.hello);
$ node test.js --hello=world
// world
$ node test.js --hello world
// world
$ node test.js --hello
// true 注意:不是空字符串而是true
➜ node git:(main) ✗ node ./minimist.js start
args: { _: [ 'start' ] }
➜ node git:(main) ✗ node ./minimist.js --start
args: { _: [], start: true }
➜ node git:(main) ✗
Commander
可以自动解析命令和参数,用于处理用户输入的命令,合并多选项,处理短参等等
npm install commander
const { program } = require('commander');
program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size')
.option('-p, --pizza-type <type>', 'flavour of pizza');
program.parse(process.argv);
const options = program.opts();
if (options.debug) console.log(options);
console.log('pizza details:');
if (options.small) console.log('- small pizza size');
if (options.pizzaType) console.log(`- ${options.pizzaType}`);
$ pizza-options -p
error: option '-p, --pizza-type <type>' argument missing
$ pizza-options -d -s -p vegetarian
{ debug: true, small: true, pizzaType: 'vegetarian' }
pizza details:
- small pizza size
- vegetarian
$ pizza-options --pizza-type=cheese
pizza details:
- cheese
通过 program.parse(arguments)
方法处理参数,没有被使用的选项会存放在 program.args
数组中。该方法的参数是可选的,默认值为 process.argv
。
CLI工具更新通知
update-notifier
控制台展示升级提醒
$ npm install update-notifier
Simple
const updateNotifier = require('update-notifier');
const pkg = require('./package.json');
updateNotifier({pkg}).notify();
Comprehensive
const updateNotifier = require('update-notifier');
const pkg = require('./package.json');
// Checks for available update and returns an instance
const notifier = updateNotifier({pkg});
// Notify using the built-in convenience method
notifier.notify();
// `notifier.update` contains some useful info about the update
console.log(notifier.update);
/*
{
latest: '1.0.1',
current: '1.0.0',
type: 'patch', // Possible values: latest, major, minor, patch, prerelease, build
name: 'pageres'
}
*/
日志打印
debug
一个模仿 Node.js 核心调试技术的小型 JavaScript 调试实用程序。适用于 Node.js 和 Web 浏览器。
$ npm install -D debug
使用案例:
var debug = require('debug')('http')
, http = require('http')
, name = 'My App';
// fake app
debug('booting %o', name);
http.createServer(function(req, res){
debug(req.method + ' ' + req.url);
res.end('hello\n');
}).listen(3000, function(){
debug('listening');
});
运行结果:
chalk-终端字符串区分展示颜色
Highlights
- Expressive API
- Highly performant
- No dependencies
- Ability to nest styles
- 256/Truecolor color support
- Auto-detects color support
- Doesn’t extend
String.prototype
- Clean and focused
- Actively maintained
- Used by ~76,000 packages as of October 26, 2021
npm install chalk
IMPORTANT: Chalk 5 is ESM. If you want to use Chalk with TypeScript or a build tool, you will probably want to use Chalk 4 for now. Read more.
import chalk from 'chalk';
console.log(chalk.blue('Hello world!'));
用户交互
inquirer
通用的命令行用户界面集合,用于和用户进行交互
npm install inquirer
var inquirer = require('inquirer');
inquirer
.prompt([
/* Pass your questions in here */
])
.then((answers) => {
// Use user feedback for... whatever!!
})
.catch((error) => {
if (error.isTtyError) {
// Prompt couldn't be rendered in the current environment
} else {
// Something else went wrong
}
});
脚本执行
execa
// 老版本 execa
const execa = require('execa')
module.exports = async function(command, options = {}) {
if (typeof options === 'string') {
options = {
cwd: options
}
}
if (/^c?npm outdated .*$/.test(command)) {
let result
try {
result = execa.shellSync(command, {
cwd: process.cwd(),
stdio: 'inherit',
...options
})
} catch (e) {
result = e
}
return Promise.resolve(result)
} else {
// 新版本 execa.commandSync API
return await execa.shellSync(command, {
cwd: process.cwd(),
stdio: 'inherit',
...options
})
}
}
这个包改进了child_process
方法:
- Promise接口
- 从输出中删除最后的换行符,这样您就不必执行
stdout.trim()
- 支持跨平台的shebang二进制文件
- 改进Windows支持。
- 更高的最大缓冲区。100mb而不是200kb。
- 按名称执行本地安装的二进制文件。
- 在父进程终止时清除派生的进程。
- 从
stdout
和stderr
获得交错输出,类似于在终端上打印的输出。(异步) - 可以指定文件和参数作为一个单一的字符串没有外壳
- 更具描述性的错误。
npm install execa
import {execa} from 'execa';
const {stdout} = await execa('echo', ['unicorns']);
console.log(stdout);
//=> 'unicorns'
基本api及用法:
- execa(file, arguments?, options?)
const { stdout } = await execa('git', ['status']);
// 指定执行目录同样是传入cwd或者process.chdir();
const { stdout } = await execa('git', ['status'], {cwd: resolve('../demo')});
复制代码
- execa.sync(file, arguments?, options?):同步执行文件
- execa.command(command, options?): 与
execa()
相同,只是文件和参数都在单个命令字符串中指定
execa.command('git status');
- execa.commandSync(command, options?):与
execa.command()
相同,但是是同步的。
shelljs
$ npm install shelljs -D
let shell = require('shelljs')
let name = process.argv[2] || 'Auto-commit';
let exec = shell.exec
if (exec('git add .').code !== 0) {
echo('Error: Git add failed')
exit(1)
}
if (exec(`git commit -am "${name}"`).code !== 0) {
echo('Error: Git commit failed')
exit(1)
}
if (exec('git push').code !== 0) {
echo('Error: Git commit failed')
exit(1)
}
持久化存储
configstore
轻松加载和持久化配置,无需考虑存储位置和方式
$ npm install configstore
import Configstore from 'configstore';
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
// Create a Configstore instance.
const config = new Configstore(packageJson.name, {foo: 'bar'});
console.log(config.get('foo'));
//=> 'bar'
config.set('awesome', true);
console.log(config.get('awesome'));
//=> true
// Use dot-notation to access nested properties.
config.set('bar.baz', true);
console.log(config.get('bar'));
//=> {baz: true}
config.delete('awesome');
console.log(config.get('awesome'));
//=> undefined
conf
允许我们在用户的机器上保存持久的信息
$ npm install conf
const Conf = require('conf');
const config = new Conf();
config.set('unicorn', '🦄');
console.log(config.get('unicorn'));
//=> '🦄'
// Use dot-notation to access nested properties
config.set('foo.bar', true);
console.log(config.get('foo'));
//=> {bar: true}
config.delete('unicorn');
console.log(config.get('unicorn'));
//=> undefined
自行简易实现
const path = require('path')
const fs = require('fs-extra')
const cacheFileName = 'index.json'
const cacheFilePath = path.join(process.cwd(), 'node_modules/.cache/test')
const filePath = path.join(cacheFilePath, cacheFileName)
function save(data={}) {
fs.ensureDirSync(cacheFilePath)
fs.writeJsonSync(filePath, data)
}
function load() {
try {
return require(filePath)
} catch(e) {
save({})
return {}
}
}
// data {}
exports.set = function(key, data) {
let localData = load()
localData[key] = data
save(localData)
}
exports.get = function(key) {
let data = load()
return data[key]
}
环境变量配置
手动配置
// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
简易封装
const enmu = {
source: 'source',
test: 'test',
development: 'development',
production: 'production',
}
// 在 env 下挂载一个 mode 变量,使用该变量作为全局环境的唯一表示
module.exports = {
setProcessMode(name) {
let result = enmu[name]
if (result) {
let arr = process.env.mode ? process.env.mode.split(',') : []
arr.push(name)
process.env.mode = [...new Set(arr)]
} else {
throw new Error('没有预置此字段')
}
},
isTest() {
return process.env.mode ? process.env.mode.split(',').includes('test') : false
},
isPrd() {
return process.env.mode ? process.env.mode.split(',').includes('production') : false
},
isDev() {
return process.env.mode ? process.env.mode.split(',').includes('development') : false
}
}
接入第三方 dotenv
Dotenv 是一个零依赖模块,它将环境变量从.env
文件加载到process.env
npm install dotenv --save
在项目的根目录创建一个 .env
文件
S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"
在项目中尽可能早的配置dotenv
require('dotenv').config()
console.log(process.env) // remove this after you've confirmed it working
扩展环境变量 dotenv-expand
dotenv-expand 在 dotenv之上添加变量扩展,扩展计算机上已经存在的环境变量
# Install locally (recommended)
npm install dotenv-expand --save
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
const dotenv = resolveApp('.env'),
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
const dotenvFiles = [
`${paths.dotenv}.${NODE_ENV}.local`,
// Don't include `.env.local` for `test` environment
// since normally you expect tests to produce the same
// results for everyone
NODE_ENV !== 'test' && `${paths.dotenv}.local`,
`${paths.dotenv}.${NODE_ENV}`,
paths.dotenv,
].filter(Boolean);
// Load environment variables from .env* files. Suppress warnings using silent
// if this file is missing. dotenv will never modify any environment variables
// that have already been set. Variable expansion is supported in .env files.
// https://github.com/motdotla/dotenv
// https://github.com/motdotla/dotenv-expand
dotenvFiles.forEach(dotenvFile => {
if (fs.existsSync(dotenvFile)) {
require('dotenv-expand')(
require('dotenv').config({
path: dotenvFile,
})
);
}
});
端口检测
detect-port-alt
使用npm包detect-port-alt
$ npm i detect-port --save
使用案例:
const detect = require('detect-port');
/**
* callback usage
*/
detect(port, (err, _port) => {
if (err) {
console.log(err);
}
if (port == _port) {
console.log(`port: ${port} was not occupied`);
} else {
console.log(`port: ${port} was occupied, try port: ${_port}`);
}
});
/**
* for a yield syntax instead of callback function implement
*/
const co = require('co');
co(function* () {
const _port = yield detect(port);
if (port == _port) {
console.log(`port: ${port} was not occupied`);
} else {
console.log(`port: ${port} was occupied, try port: ${_port}`);
}
});
/**
* use as a promise
*/
detect(port)
.then((_port) => {
if (port == _port) {
console.log(`port: ${port} was not occupied`);
} else {
console.log(`port: ${port} was occupied, try port: ${_port}`);
}
})
.catch((err) => {
console.log(err);
});
选择可用端口方法
function choosePort(host, defaultPort) {
return detect(defaultPort, host).then(
port =>
new Promise(resolve => {
if (port === defaultPort) {
return resolve(port);
}
const message =
process.platform !== 'win32' && defaultPort < 1024 && !isRoot()
? `Admin permissions are required to run a server on a port below 1024.`
: `Something is already running on port ${defaultPort}.`;
// const isInteractive = process.stdout.isTTY;
if (isInteractive) {
clearConsole();
const existingProcess = getProcessForPort(defaultPort);
const question = {
type: 'confirm',
name: 'shouldChangePort',
message:
chalk.yellow(
message +
`${existingProcess ? ` Probably:\n ${existingProcess}` : ''}`
) + '\n\nWould you like to run the app on another port instead?',
initial: true,
};
prompts(question).then(answer => {
if (answer.shouldChangePort) {
resolve(port);
} else {
resolve(null);
}
});
} else {
console.log(chalk.red(message));
resolve(null);
}
}),
err => {
throw new Error(
chalk.red(`Could not find an open port at ${chalk.bold(host)}.`) +
'\n' +
('Network error message: ' + err.message || err) +
'\n'
);
}
);
}
detect-port-alt 实现原理
[[debug包打印指定模块的日志信息]]
'use strict';
// 打印 detect-port 相关的日志信息
const debug = require('debug')('detect-port');
// 基于 net 做监听以检测端口是否可用
const net = require('net');
// 用户获取 IP 地址
const address = require('address');
module.exports = (port, host, callback) => {
if (typeof port === 'function') {
callback = port;
port = null;
} else if (typeof host === 'function') {
callback = host;
host = null;
}
port = parseInt(port) || 0;
let maxPort = port + 10;
if (maxPort > 65535) {
maxPort = 65535;
}
debug('detect free port between [%s, %s)', port, maxPort);
if (typeof callback === 'function') {
return tryListen(host, port, maxPort, callback);
}
// promise
return new Promise((resolve, reject) => {
tryListen(host, port, maxPort, (error, realPort) => {
if (error) {
reject(error);
} else {
resolve(realPort);
}
});
});
};
function tryListen(host, port, maxPort, callback) {
function handleError() {
port++;
if (port >= maxPort) {
debug(
'port: %s >= maxPort: %s, give up and use random port',
port,
maxPort
);
port = 0;
maxPort = 0;
}
tryListen(host, port, maxPort, callback);
}
// 1. check specified host (or null)
listen(port, host, (err, realPort) => {
// ignore random listening
if (port === 0) {
return callback(err, realPort);
}
if (err) {
return handleError(err);
}
// 2. check default host
listen(port, null, err => {
if (err) {
return handleError(err);
}
// 3. check localhost
listen(port, 'localhost', err => {
if (err) {
return handleError(err);
}
// 4. check current ip
let ip;
try {
ip = address.ip();
} catch (err) {
// Skip the `ip` check if `address.ip()` fails
return callback(null, realPort);
}
listen(port, ip, (err, realPort) => {
if (err) {
return handleError(err);
}
callback(null, realPort);
});
});
});
});
}
function listen(port, hostname, callback) {
const server = new net.Server();
server.on('error', err => {
debug('listen %s:%s error: %s', hostname, port, err);
server.close();
if (err.code === 'ENOTFOUND') {
debug('ignore dns ENOTFOUND error, get free %s:%s', hostname, port);
return callback(null, port);
}
return callback(err);
});
server.listen(port, hostname, () => {
port = server.address().port;
server.close();
debug('get free %s:%s', hostname, port);
return callback(null, port);
});
}
portfinder
自动寻找 8000
至65535
内可用端口号
Loading展示
ora
终端加载器
npm install ora
import ora from 'ora';
const spinner = ora('Loading unicorns').start();
setTimeout(() => {
spinner.color = 'yellow';
spinner.text = 'Loading rainbows';
}, 1000);
打开本地App
open
打开诸如 URL、文件、可执行文件之类的东西,跨平台。
$ npm install open
const open = require('open');
// Opens the image in the default image viewer and waits for the opened app to quit.
await open('unicorn.png', {wait: true});
console.log('The image viewer app quit');
// Opens the URL in the default browser.
await open('https://sindresorhus.com');
// Opens the URL in a specified browser.
await open('https://sindresorhus.com', {app: {name: 'firefox'}});
// Specify app arguments.
await open('https://sindresorhus.com', {app: {name: 'google chrome', arguments: ['--incognito']}});
// Open an app
await open.openApp('xcode');
// Open an app with arguments
await open.openApp(open.apps.chrome, {arguments: ['--incognito']});
better-opn
根据匹配的url,重用浏览器的tab页面
$ npm install better-opn
const opn = require('better-opn');
opn('http://localhost:3000');
压缩包
zip-dir
创建zip压缩包
$ npm install zip-dir
var zipdir = require('zip-dir');
// `buffer` is the buffer of the zipped file
var buffer = await zipdir('/path/to/be/zipped');
zipdir('/path/to/be/zipped', function (err, buffer) {
// `buffer` is the buffer of the zipped file
});
zipdir('/path/to/be/zipped', { saveTo: '~/myzip.zip' }, function (err, buffer) {
// `buffer` is the buffer of the zipped file
// And the buffer was saved to `~/myzip.zip`
});
// Use a filter option to prevent zipping other zip files!
// Keep in mind you have to allow a directory to descend into!
zipdir('/path/to/be/zipped', { filter: (path, stat) => !/\.zip$/.test(path) }, function (err, buffer) {
});
// Use an `each` option to call a function everytime a file is added, and receives the path
zipdir('/path/to/be/zipped', { each: path => console.log(p, "added!"), function (err, buffer) {
});
工具方法
清空控制台
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
function clearConsole() {
process.stdout.write(
process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H'
);
}
module.exports = clearConsole;
配置对象合并
function overwrite(obj1, obj2) {
const func = (o1={}, o2, key) => {
if(!key){
Object.keys(o2).forEach((v) => func(o1, o2[v], v))
} else if (toString.call(o2) === '[object Object]') {
// 对象进行递归处理
o1[key] = o1[key] || {}
Object.keys(o2).forEach((v) => {
func(o1[key], o2[v], v)
})
} else if (typeof o2 === 'function') {
// 函数接受初始值,处理后返回新的值
o1[key] = o2(o1)
} else if (Array.isArray(o2)) {
// 数组进行合并
o1[key] = o1[key] || []
o1[key] = [...new Set(o1[key].concat(o2))]
} else {
// 值直接替换
o1[key] = o2
}
return o1
}
return func(obj1, obj2)
}
也可以借助