前端脚手架开发工具包

前端脚手架开发工具包,第1张

文章目录 文件 *** 作fs-extrareaddirpevent-stream 文件匹配glob 远程下载模板代码download-git-repo 命令行参数解析**minimist轻量级的命令行参数解析引擎**Commander CLI工具更新通知update-notifierSimpleComprehensive 日志打印debugchalk-终端字符串区分展示颜色Highlights 用户交互inquirer 脚本执行execashelljs 持久化存储configstoreconf自行简易实现 环境变量配置手动配置简易封装接入第三方 dotenv扩展环境变量 dotenv-expand 端口检测detect-port-alt选择可用端口方法detect-port-alt 实现原理 portfinder Loading展示ora 打开本地Appopenbetter-opn 压缩包zip-dir 工具方法清空控制台配置对象合并

文件 *** 作 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 !
}
文件匹配 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 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

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 APIHighly performantNo dependenciesAbility to nest styles256/Truecolor color supportAuto-detects color supportDoesn’t extend String.prototypeClean 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方法:

Promise接口从输出中删除最后的换行符,这样您就不必执行stdout.trim()支持跨平台的shebang二进制文件改进Windows支持。更高的最大缓冲区。100mb而不是200kb。按名称执行本地安装的二进制文件。在父进程终止时清除派生的进程。从 stdoutstderr获得交错输出,类似于在终端上打印的输出。(异步)可以指定文件和参数作为一个单一的字符串没有外壳更具描述性的错误。
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

自动寻找 800065535内可用端口号

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)
}

也可以借助

deepmergewebpack-mergeextend

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1324838.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存