【问题标题】:How do node_modules packages read config files in the project root?node_modules 包如何读取项目根目录下的配置文件?
【发布时间】:2019-11-05 20:13:32
【问题描述】:

我正在创建一个需要能够从项目根目录读取配置文件的 npm 包。我不知道该怎么做。

例如,

  • Next.js 能够从项目根目录读取 ./pages/./next.config.js
  • Jest 能够从项目根目录读取 ./jest.config.js
  • ESLint 能够从项目根目录读取./.eslintrc.json
  • Prettier 能够从项目根目录读取./.prettierrc.js
  • Typescript 能够从项目根目录读取./tsconfig.json
  • Babel 能够从项目根目录读取./.babelrc

我尝试查看他们的源代码以了解他们是如何做到的,但项目太大,我找不到相关部分。

他们是如何做到这一点的?

【问题讨论】:

  • 通过诸如; require()fs.readFile()fs.readFileSync()。如果使用最后两种方法中的任何一种,则如果配置文件是 JSON,则它们随后会 JSON.parse() 内容。例如 eslintrc.json 使用 fs.readFileSync.
  • 项目根的构成没有标准。这就是为什么例如ESLint 将从当前目录(或用户提供的另一个目录)到文件系统根目录查找.eslintrc 文件。 npm 在搜索 package.json 时会做类似的事情,除了它在第一次匹配时停止。

标签: javascript node.js npm node-modules


【解决方案1】:

首先在path.dirname(process.mainModule.filename) 中搜索,然后在目录树上搜索../ ../../,依此类推,直到找到该文件。

这是我从 rc (https://github.com/dominictarr/rc) 包中窃取的代码:

const fs = require('fs');
const path = require('path');

// Utils shamefully stolen from
// https://github.com/dominictarr/rc/blob/master/lib/utils.js

find(...args) {
  const rel = path.join.apply(null, [].slice.call(args));
  return findStartingWith(path.dirname(process.mainModule.filename), rel);
}

findStartingWith(start, rel) {
  const file = path.join(start, rel);
  try {
    fs.statSync(file);
    return file;
  } catch (err) {
    // They are equal for root dir
    if (path.dirname(start) !== start) {
      return findStartingWith(path.dirname(start), rel);
    }
  }
}

parse(content) {
  if (/^\s*{/.test(content)) {
    return JSON.parse(content);
  }
  return undefined;
}

file(...args) {
  const nonNullArgs = [].slice.call(args).filter(arg => arg != null);

  // path.join breaks if it's a not a string, so just skip this.
  for (let i = 0; i < nonNullArgs.length; i++) {
    if (typeof nonNullArgs[i] !== 'string') {
      return;
    }
  }

  const file = path.join.apply(null, nonNullArgs);
  try {
    return fs.readFileSync(file, 'utf-8');
  } catch (err) {
    return undefined;
  }
}

json(...args) {
  const content = file.apply(null, args);
  return content ? parse(content) : null;
}

// Find the rc file path
const rcPath = find('.projectrc');
// Or
// const rcPath = find('/.config', '.projectrc');

// Read the contents as json
const rcObject = json(rcPath);
console.log(rcObject);

你也可以使用 rc 包作为依赖 npm i rc

请注意,这是同步,因此它会阻止代码执行,直到它完成。

【讨论】:

    【解决方案2】:

    它们从文件所在的目录开始,递归地在文件系统树中向上查找,直到找到它要查找的文件。

    类似这样的:

    const FILE_NAME = 'target-file.json';
    
    const fsp = require('fs').promises,
          path = require('path');
    
    let find = async (dir=__dirname) => {
      let ls = await fsp.readdir(dir);
      if(ls.includes(FILE_NAME))
        return path.join(dir,FILE_NAME);
      else if(dir == '/')
        throw new Error(`Could not find ${FILE_NAME}`);
      else
        return find(path.resolve(dir,'..'));
    }
    

    或者,如果您正在寻找标准节点“项目根”,您可能想要递归并找到一个包含目录名称“node_modules”的目录,如下所示:

    const fsp = require('fs').promises,
          path = require('path');
    
    let find = async (dir=__dirname) => {
      let ls = await fsp.readdir(dir);
      if(ls.includes('node_modules'))
        return dir;
      else if(dir == '/')
        throw new Error(`Could not find project root`);
      else
        return find(path.resolve(dir,'..'));
    }
    

    【讨论】:

      【解决方案3】:

      有多种方法可以做到这一点。我创建了一个test-package 和一个演示项目node-package-test 来测试它。

      此处提供主要代码以供参考:

      project-main\node_modules\test-package\index.js:

      const path = require('path');
      const fs = require('fs');
      
      const CONFIG_NAME = 'cfg.json';
      
      function init(rootDir = null) {
        console.log(`test-package: process.cwd(): ${process.cwd()}`);
        console.log(`test-package: path.resolve('./'): ${path.resolve('./')}`);
      
        if (!rootDir) {
          //rootDir = path.resolve('./');
          // OR
          rootDir = process.cwd();
        }
      
        //const configPath = path.resolve('./', CONFIG_NAME);
        // OR
        const configPath = path.join(rootDir, CONFIG_NAME);
      
      
        if (fs.existsSync(configPath)) {
          console.log(`test-package: Reading config from: ${configPath}`);
          try {
            //const data = fs.readFileSync(configPath, 'utf8');
            //const config = JSON.parse(data);
            // OR
            const config = require(configPath);
            console.log(config);
          } catch (err) {
            console.error(err)
          }
        } else {
      
          console.log(`test-package: Couldn't find config file ${configPath}. Using default.`)
        }
      
        console.log('\n')
      }
      
      //init()
      const features = {
        init: init,
        message: `Hello from test-package! ?`
      }
      
      
      module.exports = features;
      

      项目主\main.js:

      const utils = require('@onkarruikar/test-package')
      
      utils.init();
      // OR use
      //utils.init('/path/to/rootdir');
      
      console.log(`${utils.message}`);
      

      输出:

      E:\node-package-test-main>npm install
      
      added 1 package, and audited 2 packages in 4s
      
      found 0 vulnerabilities
      
      E:\node-package-test-main>npm start
      
      > start
      > node .
      
      test-package: process.cwd(): E:\node-package-test-main
      test-package: path.resolve('./'): E:\node-package-test-main
      test-package: Reading config from: E:\node-package-test-main\cfg.json
      { compilerOptions: { allowJs: true, checkJs: true, noEmit: true } }
      
      
      Hello from test-package! ?
      

      【讨论】:

      • 如果是js或者json文件,使用require会更方便。然后你就不能像对象一样直接访问它了。不需要去 json.parse。
      • 是的,可以使用。如果您允许其他扩展,那么您需要首先检查根中存在哪个扩展,然后相应地加载。
      • 我们讲的包在用户包的node_modules中,所以如果它运行这个脚本,会对用户包产生副作用(比如我们把这个包安装在里面的nextjs应用程序,它将在客户端运行,并导致错误),除此之外,我们无法使用此脚本从node_modules读取用户包中的文件
      • “客户端”是指在用户的网络浏览器中吗?这是how JestJs 做到了。为了更好地理解,让我们知道您的软件包将提供哪些功能。它与Web应用程序有关吗?我已经更新了答案。安装并运行 node-package-test 应用程序,如果您正在寻找不同的行为,请告诉我们。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-03
      • 1970-01-01
      • 2014-07-16
      • 1970-01-01
      • 2017-04-01
      • 1970-01-01
      相关资源
      最近更新 更多