Skip to content

Latest commit

 

History

History
419 lines (406 loc) · 12.5 KB

readme.md

File metadata and controls

419 lines (406 loc) · 12.5 KB

前言

这部分主要讲述了在开发类似于CLI命令中遇到的问题,是一些很值得关注的问题。如果喜欢记得star,如果有任意问题也欢迎issue。

1.开发自己的cli命令报错

bin目录下的文件名称和package.json中的bin配置的文件名称是配套的,如果bin下的文件有后缀那么package.json中也必须有后缀:

   "bin": {
        "extract": "./bin/extract.js"
    }

2.glob抓取文件忽略某一个具体的文件夹

const glob = require("glob");
const files = glob
  .sync("**", { dot: false,cwd, ignore: "node_modules/**" })
  .filter(file => {
    return path.extname(file) === ".js" || path.extname(file) === ".jsx";
  })
  .reduce((prev, cur) => {
    return prev.concat(path.join(cwd, cur));
  }, []);

比如上面的例子就是抓取所有的js和jsx后缀文件,但是忽略node_modules下的额外文件。需要注意的是ignore的配置。

3.监听某一个目录下动态生成的文件夹

const chokidar = require("chokidar");
const watchDir = 'xxxx';
// 需要监听的目录
let watcher = chokidar
  .watch(watchDir, {
    persistent: true,
    disableGlobbing: true,
    ignoreInitial: true,
    // 1.忽略一开始就有的某一个目录下的文件夹
    depth: 0
  })
  .on("addDir", genPath => {
    // 2.监听到文件被生成,即hub生成的文件
    const relativePath = path.relative(genPath, watchDir);
    const index = genPath.indexOf(watchDir);
    let genHubPath = genPath.substring(index + watchDir.length + 1);
    // 3.获取产生的目录名称
    if (relativePath == "..") {
        // 4.表示是动态产生的目录,此时获取到产生的目录可以操作了
    }
  });

4.动态产生React组件的props构建jsx的template

const path2Cnpt = '';
// 需要解析的某一个组件的地址
const fakeProps = require("react-fake-props");
const props = fakeProps(path2Cnpt, { optional: true });
// optional: true表示非required的属性也要动态产生
// 产生了props就可以动态构建jsx的template了

下面通过动态产生的props构建jsx的template:

//cntName:组件名称
//props:动态产生的组件props
function generateTestTemplate(cntName, props) {
  const template = `
    import React from 'react';
    import ReactDOM from 'react-dom';
    import ${cntName} from './index';
    const props = ${JSON.stringify(props)};
    class Test extends React.Component {
      render() {
        return <${cntName} {...props}\/>;
      }
    }
    ReactDOM.render(<Test />, document.getElementById('app'));
    `;
  return template;
}

有一点需要注意:那就是react-fake-props产生的props是对象,即object,所以动态产生template的时候需要stringify一下:

const props = ${JSON.stringify(props)};

5.获取某一个文件的绝对路径使用require.resolve(node v8+支持)

  const path = require('path');
  const sourcePath = require.resolve(
    path.resolve(`${cwd}`, relative[r])
  );
  let fileName = path.basename(sourcePath);
  //通过文件名称引入的,比如require('./util/test')表示util下的test.js
  if (fileName != "index.js") {
    const arr = destPath.split(path.sep);
    arr.pop();
    destPath = arr.join(path.sep);
    // 去掉文件名就是它的目录
  }

如果要兼容低版本可以使用npm上的resolve模块来完成。

6.执行cli命令并支持输入

很多情况下需要在自己的cli命令中包装别人的cli命令,而别人的cli命令是yoeman开发的,需要支持用户的输入,此时你可能想到的是使用shelljs,但实际上官方明确说了不支持此类用法,可以采用如下的方式来完成。

const child_process = require("child_process");
// 假如原有的cli命令,即包装命令是:et init liangklfangl
child_process.execFileSync("et", ["init", "liangklfangl"], {
    stdio: "inherit"
  });
7.正则表达式在node中判断是否是相对路径
const pathRegex = /^\./g;
const isRelative = pathRegex.test(importSource);
// 第一种方法判断是否是相对路径,但是有时候会出现很奇怪的结果,所以我都使用如下的方式
const isRelative = path2Detect.indexOf(".") == 0;
8.通过shell安装某些npm包
const child_process = require("child_process");
const exec = child_process.exec;
/**
 * 执行shell脚本
 */
function execute(cmd, workingDirPath) {
  return new Promise((resolve, reject) => {
    try {
      let child = exec(
        cmd,
        { cwd: workingDirPath },
        (error, stdout, stderr) => {
          if (error) {
            reject(error);
          } else {
            resolve(stdout);
          }
        }
      );
    } catch (e) {
      reject(e);
    }
  });
}
/**
 * npm配置
 */
function getNpmConfigVariable(varName, workingDirPath) {
  return execute(`tnpm get ${varName}`, workingDirPath).catch(err => {
    console.error(err);
    return undefined;
  });
}
/**
 * 设置npm配置
 */
function setNpmConfigVariable(varName, varValue, workingDirPath) {
  return execute(
    `tnpm set ${varName}=${varValue}`,
    workingDirPath
  ).catch(err => {
    console.error(err);
    return undefined;
  });
}
/**
 * 安装特定模块
 */
const installPackages = function(pkgNames, workingDirPath) {
  let oldProgress;
  return getNpmConfigVariable("progress", workingDirPath)
    .then(result => {
      oldProgress = result;
      return setNpmConfigVariable("progress", "false", workingDirPath);
    })
    .then(() => {
      log(chalk.green(`准备开始安装${pkgNames}依赖`));
      return execute(`tnpm install ${pkgNames} -S -E`, workingDirPath);
    })
    .then(() => {
      log(chalk.green(`依赖的${pkgNames}模块安装完成!`));
      return setNpmConfigVariable("progress", oldProgress, workingDirPath);
    });
};

以上方法来自于structor-commons

9.如何在nodejs中因为权限问题提升为sudo模式
var os;
os = require('os');
exports.execute = function(command, callback) {
  var commander;
  if (os.platform() === 'win32') {
    commander = require('./windows');
  } else {
    commander = require('./unix');
  }
  return commander.executeWithPrivileges(command, callback);
};

具体你可以点击这里

10.nodejs中关于npm常见nodejs方法使用

以下所有内容来自于selfupdate模块。

var async, npm, utils, _;
npm = require('npm');
async = require('async');
_ = require('lodash-contrib');
utils = require('./utils');
/**
 * 得到一个npm包的信息
 */
exports.getInfo = function(name, callback) {
  // https://github.com/liangklfangl/react-article-bucket/tree/master/async-programing/async-js#43-asyncify%E7%94%A8%E4%BA%8Eparallel%E6%96%B9%E6%B3%95
  return async.waterfall([
    function(callback) {
      var options;
      options = {
        loglevel: 'silent',
        global: true
      };
      return npm.load(options, _.unary(callback));
      // unary:Creates a function that accepts up to one argument, ignoring any additional arguments.
    }, function(callback) {
      //npm view命令
      return npm.commands.view([name], true, callback);
    }
  ], callback);
};
/**
 * 得到npm包的最新版本
 */
exports.getLatestVersion = function(name, callback) {
  return exports.getInfo(name, function(error, data) {
    var versions;
    if (error != null) {
      return callback(error);
    }
    versions = _.keys(data);
    return callback(null, _.first(versions));
  });
};
/**
 * 判断某一个包是否在最新的V版本上
 */
exports.isUpdated = function(packageJSON, callback) {
  return exports.getLatestVersion(packageJSON.name, function(error, latestVersion) {
    if (error != null) {
      return callback(error);
    }
    return callback(null, packageJSON.version === latestVersion);
  });
};
/**
 * 更新一个npm包
 */
exports.update = function(packageJSON, callback) {
  return async.waterfall([
    function(callback) {
      var command;
      command = utils.getUpdateCommand(packageJSON.name);
      return utils.runCommand(command, callback);
    }, function(stdout, stderr, callback) {
      if (!_.isEmpty(stderr)) {
        return callback(new Error(stderr));
      }
      return exports.getLatestVersion(packageJSON.name, callback);
    }
  ], callback);
};

下面是utils模块提供的方法包括getUpdateCommand/runCommand:

var child_process, president, _;
president = require('president');
_ = require('lodash-contrib');
child_process = require('child_process');
/**
 * 得到更新的命令
 */
exports.getUpdateCommand = function(name) {
  if (name == null) {
    throw new Error('Missing package');
  }
  return "npm install --silent --global " + name;
};
/**
 * 判断一个错误是否是权限相关的
 */
exports.isPermissionError = function(error) {
  if (error == null) {
    return false;
  }
  if (error.message == null) {
    error.message = '';
  }
  return _.any([error.code === 3, error.errno === 3, error.code === 'EPERM', error.code === 'EACCES', error.code === 'ACCES', error.message.indexOf('EACCES') !== -1]);
};
/**
 *  执行某一个cli命令,如果执行失败就提升为sudo权限
 */
exports.runCommand = function(command, callback) {
  return child_process.exec(command, function(error, stdout, stderr) {
    if (_.any([exports.isPermissionError(error), exports.isPermissionError(new Error(stderr))])) {
      return president.execute(command, callback);
    }
    return callback(error, stdout, stderr);
  });
};

那么selfupdate模块如何使用呢?下面来自于zan-proxy:

import childProcess from 'child_process';
import { promisify } from 'es6-promisify';
import { prompt } from 'inquirer';
import ora from 'ora';
// https://github.com/sindresorhus/ora
import selfupdate from 'selfupdate';
// 来自于上面分析的selfupdate
const packageInfo = require('../../package');
// 需要检查全局自动更新的package.json
const update = promisify(selfupdate.update);
const isUpdated = promisify(selfupdate.isUpdated);
// update方法和isupdated方
const exec = promisify(childProcess.exec);
// promisify方法
export default async () => {
  const checkSpinner = ora('正在检查更新...').start();
  const isLatest = await isUpdated(packageInfo);
  checkSpinner.stop();
  if (isLatest) {
    console.log('当前已是最新版本');
    return;
  }
  const ans = await prompt([
    {
      message: '检测到新版本,是否更新?',
      name: 'shouldUpdate',
      type: 'confirm',
    },
  ]);
  // 用户输入
  if (!ans.shouldUpdate) {
    return;
  }
  const updateSpinner = ora('正在更新').start();
  //开始更新
  return update(packageInfo)
    .catch(() => exec(`npm uninstall --global --silent ${packageInfo.name}`))
    // 已经安装了删除掉,重新安装一遍~~
    .then(() => exec(`npm install --global --silent ${packageInfo.name}`))
    .then(() => {
      updateSpinner.stop();
      console.log(
        `更新完成,请重新启动! 如出现命令丢失情况,请手动重新安装:npm install -g ${
          packageInfo.name
        }`,
      );
      process.exit(0);
    })
    .catch(error => {
      console.error('更新失败', error);
      process.exit(1);
    });
};
11.nodejs中阻塞当前进程等待用户输入inquery.js
 const ans = await prompt([
    {
      message: '检测到新版本,是否更新?',
      name: 'shouldUpdate',
      type: 'confirm',
    },
  ]);
 // shouldUpdate表示用户是否选择了自动更新
  if (!ans.shouldUpdate) {
     return;
  }
12.npm模块的常见方法

上面已经简单说了npm的基本使用方法,里面还包括的方法有:

config: { loaded: false, get: [Object], set: [Object] },
commands: {ac,}
// commands里面包含所有的方法

建议自己require它并打印出来。

13.babel打包的时候拷贝非js文件

遇到一个常见的问题:在babel打包的时候需要考虑less/scss等文件,此时可以直接使用如下的命令即可:

rimraf lib && babel src --out-dir lib --copy-files
14.git分支排序
const semver = require('semver')
var gits = [
  "daily/0.0.10",
  "daily/0.0.11",
  "daily/0.0.12",
  "daily/0.0.13",
  "daily/0.0.2",
  "daily/0.0.5",
  "daily/0.0.6",
  "daily/0.0.7",
  "daily/0.0.8"
]
const sorted = gits.sort(function(a, b) {
    var lt = semver.coerce(a);
    var gl = semver.coerce(b);
    return semver.gt(lt, gl);
})