最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do node_modules packages read config files in the project root? - Stack Overflow

programmeradmin1浏览0评论

I am creating an npm package that needs to be able to read config files from the project root. I'm not sure how to do this.

For example,

  • Next.js is able to read ./pages/ and ./next.config.js from the project root
  • Jest is able to read ./jest.config.js from the project root
  • ESLint is able to read ./.eslintrc.json from the project root
  • Prettier is able to read ./.prettierrc.js from the project root
  • Typescript is able to read ./tsconfig.json from the project root
  • Babel is able to read ./.babelrc from the project root

I've tried looking at their source code to see how they do it but the projects are so large that I can't find the relevant section.

How do they achieve this?

I am creating an npm package that needs to be able to read config files from the project root. I'm not sure how to do this.

For example,

  • Next.js is able to read ./pages/ and ./next.config.js from the project root
  • Jest is able to read ./jest.config.js from the project root
  • ESLint is able to read ./.eslintrc.json from the project root
  • Prettier is able to read ./.prettierrc.js from the project root
  • Typescript is able to read ./tsconfig.json from the project root
  • Babel is able to read ./.babelrc from the project root

I've tried looking at their source code to see how they do it but the projects are so large that I can't find the relevant section.

How do they achieve this?

Share Improve this question edited Jun 26, 2019 at 5:40 Jake asked Jun 24, 2019 at 3:25 JakeJake 4,2556 gold badges33 silver badges64 bronze badges 2
  • 2 Via methods such as; require(), fs.readFile(), or fs.readFileSync(). If either of the last two methods are used they subsequently JSON.parse() content if config file is JSON. For instance eslintrc.json utilizes fs.readFileSync. – RobC Commented Jun 30, 2019 at 16:47
  • There is no standard for what constitutes a project root. That is why e.g. ESLint will look up for .eslintrc files starting from the current directory (or from another directory provided by the user) up to the file system root. npm does something similar when searching for package.json, except that it stops on the first match. – GOTO 0 Commented Feb 10, 2022 at 15:16
Add a comment  | 

5 Answers 5

Reset to default 4

First search in path.dirname(process.mainModule.filename) then go up the directory tree ../, ../../, ../../../ and so on until you find the configuration file.

Here is the code I use stolen from rc (https://github.com/dominictarr/rc) package, it will read and json parse configuration from a file named .projectrc:

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

You can also use the rc package as a dependency npm i rc, then in your code:

var configuration = require('rc')(appname, {
  // Default configuration goes here
  port: 2468
});

This will read config from a file named .${appname}rc.

Ran into this problem when I made my first npm package. The findup-sync library solves this nicely:

const findup = require('findup-sync');
const filePath = findup('filename');

https://www.npmjs.com/package/findup-sync

They start from the directory the file lives in and recursively looks upwards in the file system tree until it finds the file it's looks for.

Something like this:

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,'..'));
}

Or if you were looking for a standard node "project root" you might want to recurse up and find a directory containing a directory names 'node_modules' like this:

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,'..'));
}

I was also looking to solve similar problem with my npm package and found out these 2 modules. I have used lilconfig as its smaller in size and satisfies my use case.

lilconfig (16.6 kB)

const {lilconfigSync} = require('lilconfig');
const configSync =  lilconfigSync("filename");
const config = configSync.search();

cosmiconfig (111 kB)

const { cosmiconfigSync } = require('cosmiconfig');
const explorerSync = cosmiconfigSync("filename");
const searchedFor = explorerSync.search();

There are multiple ways to do it. I've created a test-package, and a demo project node-package-test to test it.

For ready reference providing the main code here:

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! 
发布评论

评论列表(0)

  1. 暂无评论