likes
comments
collection
share

用depcheck来解决工程项目的灵异事件

作者站长头像
站长
· 阅读数 26

前言

你是否遇到以下问题

    1. GitHub上面 clone 的项目,运行报错…
    1. 去一家新公司,用新电脑运行项目各种报错…
    1. 明明这个项目在公司电脑上运行正常,用我自己的电脑运行就各种错误,运行不起来?
    1. 项目在别人那里运行的好好地,拿到自己这边就跑不起来了…

前端在项目开发中,多多少少会遇见 npm run serve 跑不起来项目的问题,本文用 depcheck 来帮助你进行项目依赖自检,让你不再过多的关心项目的灵异事件。项目 package.json 长这样:

用depcheck来解决工程项目的灵异事件

场景

当我们 clone 目录下来,在本地 npm i 的之后,再去执行 npm run dev ,可能出现两种情况:

  • case1

用depcheck来解决工程项目的灵异事件

  • case2

用depcheck来解决工程项目的灵异事件

这种情况很常见,那么我们可以利用 depcheck 这个包来协助我们了。

使用

// 安装
npm i -g depcheck;

// 执行
depcheck;

结果如下:

用depcheck来解决工程项目的灵异事件

Unused 表示没有使用的依赖包,Missing 表示使用到了但是没有在 json 文件中声明的依赖包

运行结果显示,没有用到的本地依赖,开发依赖,缺失的依赖。所以我们可以按照他的提示来安装一下依赖,就能够运行啦!

常用的参数

--skip-missing=[true | false] 默认false,表示是否检测Missing的依赖包

--ignore-bin-package=[true | false] 默认false,表示是否忽略包含bin条目的包

--json 表示所有包的检测结果以 json 格式输出,大概就是XX包在哪些文件使用了,{"包名": ["path1","path2"]}

--ignores="eslint,babel-" 表示要忽略的包名称(逗号分隔),比如 depcheck --ignores="eslint,@babel/,babel-*"

--ignore-path 表示要忽略的文件的模式的文件的路径,比如 depcheck --ignore-path=.eslintignore

--ignore-dirs 已经弃用,使用 --ignore-patterns 替代,表示要忽略的目录名,逗号分隔--ignore-dirs=dist,coverage.

--ignore-patterns 表示要忽略的用逗号分隔的模式描述文件,比如depcheck --ignore-patterns=build/Release,dist,coverage,*.log.

  • --parsers
  • --detectors
  • --specials

高级的语法使用参考官方文档

ignore

可能这个包实现的是有一些问题,比如 prettier 包明显有使用到,为什么会被列入 Unused devDependencies 中呢? 那么这里的解决办法就是新建 .depcheckrc 文件,把这个写到里面去。

ignores: ["eslint", "babel-*", "@babel/*", "@vue/*", "prettier"]
skip-missing: true // 表示不呈现项目中使用到但是在package.json里面没有的包,默认为false

运行结果:

用depcheck来解决工程项目的灵异事件

源码解读

  • github仓库
  • 描述:
    • 全局安装
    • depcheck 可带可不带行内参数

入口文件

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

depcheck.js

// #!/usr/bin/env node
require('please-upgrade-node')(require('../package.json'));

/* eslint-disable no-console */

require('../dist/cli')(
  process.argv.slice(2),
  console.log,
  console.error,
  function exit(code) {
    process.exitCode = code;
  },
);

CommonJS 写法:

require('please-upgrade-node')(require('../package.json')), 拆分为:

const pleaseUpgradeNode = require('please-upgrade-node');
const pkg = require('../package.json');
pleaseUpgradeNode(pkg);
  • 这个包把 package.json 传进去,就为了获取规定的 nodejs 版本,与当前版本对比,给予及时更新 node 版本的逻辑。
  • 之后便是导入 dist 目录下的 cli ,就是导入的 cli 函数。

cli

async function cli(args, log, error, exit) {
  try {
    const opt = 
    await (0, _configurationReader.getConfiguration)(args, _package.name, _package.version);
    const dir = opt._[0] || '.';

    const rootDir = _path.default.resolve(dir); // 获取到的根路径 /Users/mac/Desktop/depCheck

    await checkPathExist(rootDir, `Path ${dir} does not exist`); // 检查根目录是否存在
    await checkPathExist(_path.default.resolve(rootDir, 'package.json'), `Path ${dir} does not contain a package.json file`); // 检查package.json文件是否存在于根目录
    
    // 找到dependencies、devDependencies、invalidDirs、invalidFiles、missing、using
    const depcheckResult = await (0, _index.default)(rootDir, {
      ignoreBinPackage: opt.ignoreBinPackage,
      ignorePath: opt.ignorePath,
      ignoreMatches: opt.ignores || [],
      ignoreDirs: opt.ignoreDirs || [],
      ignorePatterns: opt.ignorePatterns || [],
      parsers: getParsers(opt.parsers),
      detectors: getDetectors(opt.detectors),
      specials: getSpecials(opt.specials),
      skipMissing: opt.skipMissing
    });
    print(depcheckResult, log, opt, rootDir);  // 控制台输出 Unused、Missing等
    exit(noIssue(depcheckResult) ? 0 : -1);
  } catch (err) {
    error(err);
    exit(-1);
  }
}

其中我们发现了这样的一段代码 (0, _configurationReader.getConfiguration)(args, _package.name, _package.version),我们简化成 (0, fn)(params)

(0, fn)(params) 0的作用

  • 1.绑定(重置)this 指向,指向全局对象——最外层 windowglobalThis
  • 2.避免了有些源码中会修改 prototype ,导致原型链上方法不可用的情况

上述代码最关键的就是opt,一切都是围绕着opt展开的,那我们来一起看看opt得到的是什么吧。

获得opt

exports.getCliArgs = getCliArgs;
exports.getRCFileConfiguration = getRCFileConfiguration;
exports.getConfiguration = getConfiguration;

...
async function getConfiguration(args, moduleName, version) {
  // args: []
  // moduleName: depcheck
  // version: 0.0.1
  const dir = args[0] || '.'; // dir 取相对路径
  const cliConfig = getCliArgs(args, version); // 获得cliConfig
  const rcConfig = await getRCFileConfiguration(moduleName, cliConfig.argv.config, dir);
  return { ...rcConfig, // ignorerc文件
    ...cliConfig.argv // 暴露方法
  };
}

opt.oneline 来记录在哪一行使用到了依赖导入,针对于Missing的依赖才记录,Unused的不记录。

得到的opt长下面这样子:

用depcheck来解决工程项目的灵异事件

其余的大概没有什么了,最后就是获取到的一些依赖进行打印。

print

function print(result, log, opt, rootDir) {
  if (opt.json) { // json标识
    log(JSON.stringify(result, (key, value) => _lodash.default.isError(value) ? value.stack : value));
  } else if (noIssue(result)) {
    log('No depcheck issue'); // 没有结果
  } else { // 有depcheckResult 输出
  
    // 组装deps
    const deps = prettify('Unused dependencies', result.dependencies, opt.oneline);
    // 组装devDeps
    const devDeps = prettify('Unused devDependencies', result.devDependencies, opt.oneline);
    // 组装Missing
    const missing = prettify('Missing dependencies', mapMissing(result.missing, rootDir, opt.oneline), opt.oneline);
    
    // 组装deps、 devDeps、 Missing 输出数组
    const content = deps.concat(devDeps, missing).join('\n');
    log(content);
  }

  return result; // ??? 这里为什么要返回result,有点多此一举的感觉
}

总结

depcheck能够帮助解决前言中的问题,让你的项目不再出现灵异事件,用你专注于业务代码开发。