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 !
}
文件匹配
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
的方式
node.js的命令行参数解析工具有很多,比如:argparse、optimist、yars、commander。optimist和yargs内部使用的解析引擎正是minimist,如果你喜欢轻量级的技术,那么minimist足够简单好用,代码量也很少(只有几百行),非常适合研读。
minimist的特性比较全面:
short optionslong optionsBoolean 和 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 ' , '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 ' 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
。
控制台展示升级提醒
$ 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');
});
运行结果:
String.prototype
Clean and focusedActively maintainedUsed 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
方法:
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
内可用端口号
终端加载器
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)
}
也可以借助
deepmergewebpack-mergeextend欢迎分享,转载请注明来源:内存溢出
评论列表(0条)