Skip to content

Commit

Permalink
refactor(load-esm-config): docs and renaming
Browse files Browse the repository at this point in the history
  • Loading branch information
ifiokjr committed May 30, 2022
1 parent bf345f0 commit 04ba7ca
Show file tree
Hide file tree
Showing 6 changed files with 575 additions and 378 deletions.
12 changes: 12 additions & 0 deletions .changeset/tricky-cycles-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'load-esm-config': minor
---

**BREAKING**: Rename `lookupFilesToRoot` option to `disableUpwardLookup`. The default is now for `loadEsmConfig` to search upward until it reaches the root directory. You can set this option to true to only search the provided working directory (`cwd`).

**BREAKING**: Rename interface `LoadEsmConfig` to `LoadEsmConfigOptions`.

Other changes:

- Improved the readme for better npm documentation.
- Refactored the code to be more readable.
171 changes: 169 additions & 2 deletions packages/load-esm-config/readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,172 @@
# @monots/config
# load-esm-config

> Load configuration files written in TypeScript and JavaScript
This package is adapted from [vite](https://github.com/vitejs/vite/blob/80dd2dfd8049c39e516e19ad5cfdaa1c5f02e4a3/packages/vite/src/node/config.ts).
## Why?

Tools like `vite` have popularized the idea of code as configuration within the JavaScript / TypeScript ecosystem. Some advantages of using TypeScript for are:

- increased flexibility around apis
- type-safety for users
- inline documentation for an enhanced learning process

This project is solely focused on automating the process of loading these configuration files. The configuration is searched from the provided `cwd` and loaded automatically.

- support for both commonjs and esm modules
- nested configuration folders (by default configuration can be located in the `.config` folder)

## Installation

```bash
pnpm add load-esm-config
```

## Usage

```ts
import { loadEsmConfig } from 'load-esm-config';

// Load the configuration file and the absolute path to the config file.
const result = loadEsmConfig({ name: 'something' });

// `result` is either undefined or an object with the following properties:
if (result) {
const { config, path } = result;
}
```

By default it supports these extensions: `['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs']`. It is also possible to limit support for extensions by passing the `extensions` property.

By default `./` (current) and `.config/` folders will be searched for matching configuration files. In the example above both `something.config.ts` and `.config/something.config.mjs` are valid configuration locations. This optionality should help remove configuration file overload in top level Further configuration folders can be added via the `dirs` property.

## API

### `loadEsmConfig`

<table><tr><td width="400px" valign="top">

### `LoadEsmConfigOptions`

These are the options that can be provided to the `loadEsmConfig` function.

</td><td width="600px"><br>

````ts
/**
* @template Config - the type of configuration that will be loaded
* @template Argument - the argument that is passed to the configuration if is
* supports being called.
*/
export interface LoadEsmConfigOptions<Config extends object = object, Argument = unknown> {
/**
* The name of the configuration object to search for.
*
* ### Example
*
* The following will search for the files from the provided current working
* directory.
*
* - `monots.config.js`
* - `monots.config.ts`
*
* ```
* await loadEsmConfig({name: 'monots'});
* ```
*/
name: string;

/**
* The directory to search from.
*
* @default process.cwd()
*/
cwd?: string;

/**
* The initial configuration which will be used to set the defaults for the
* provided configuration.
*/
config?: PartialDeep<Config>;

/**
* The extensions to support.
*
* @default ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs']
*/
extensions?: SupportedExtensions[];

/**
* The same level configuration directories which should also be searched.
*
* The order of the directories determines the priority. By default
* `.config/name.config.js` is preferred to `name.config.ts`.
*
* @default ['.config', '']
*/
dirs?: string[];

/**
* If your configuration object supports being called with an argument, this
* is used to generate the argument.
*
* It takes the options passed to `loadEsmConfig` and returns your desired
* configuration.
*
* @default () => {}
*/
getArgument?: (options: LoadEsmConfig<Config>) => Argument;

/**
* Overwrite the way certain properties are merged.
*
* @see https://github.com/TehShrike/deepmerge#custommerge
*/
mergeOptions?: DeepMergeOptions;

/**
* By default the configuration file is searched from the provided working
* directory, but if not found, each parent directory will also be searched,
* all the way to the root directory.
*
* If this behaviour is not desired, set this to `false`.
*
* @default false
*/
disableUpwardLookup?: boolean;
}
````

</td></tr></table>

<table><tr><td width="400px" valign="top">

### `interface LoadEsmConfigResult`

The value returned when calling `loadEsmConfig`. This will either be `undefined` or an object with the properties shown.

</td><td width="600px"><br>

```ts
interface LoadEsmConfigResult<Config extends object = any> {
/**
* The absolute path to the resolved configuration file.
*/
path: string;

/**
* The configuration object that was loaded.
*/
config: Config;

/**
* All the dependencies encountered while loading the file.
*/
dependencies: string[];
}
```

</td></tr></table>

## Gratitude

This package is adapted from [vite](https://github.com/vitejs/vite/blob/80dd2dfd8049c39e516e19ad5cfdaa1c5f02e4a3/packages/vite/src/node/config.ts). The work the vite developers are doing is outstanding and this package would not exist without their efforts.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createDebugger } from '@monots/utils';
import type { ValueOf } from 'type-fest';

export const DEFAULT_GET_ARGUMENT = () => {};
export const SUPPORTED_EXTENSIONS = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'] as const;
export type SupportedExtensions = ValueOf<typeof SUPPORTED_EXTENSIONS, number>;
export const debug = createDebugger('monots:load-esm-config');
141 changes: 141 additions & 0 deletions packages/load-esm-config/src/config-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { DeepMergeOptions } from '@monots/utils';
import type { PartialDeep } from 'type-fest';

import { type SupportedExtensions } from './config-constants.js';

type ConfigAsFunction<Config extends object, Argument = unknown> = (
argument: Argument,
) => Config | Promise<Config>;
export type ExportedConfig<Config extends object, Argument = unknown> =
| Config
| Promise<Config>
| ConfigAsFunction<Config, Argument>;

/**
* Taken from https://github.com/vitejs/vite/blob/80dd2dfd8049c39e516e19ad5cfdaa1c5f02e4a3/packages/vite/src/node/config.ts#L832-L834
*/
export interface NodeModuleWithCompile extends NodeModule {
_compile: (code: string, filename: string) => any;
}

/**
* @template Config - the type of configuration that will be loaded.
* @template Argument - the argument that is passed to the configuration if is
* supports being called.
*/
export interface LoadEsmConfigOptions<Config extends object = object, Argument = unknown> {
/**
* The name of the configuration object to search for.
*
* ### Example
*
* The following will search for the files from the provided current working
* directory.
*
* - `monots.config.js`
* - `monots.config.ts`
*
* ```
* await loadEsmConfig({name: 'monots'});
* ```
*/
name: string;

/**
* The directory to search from.
*
* @default process.cwd()
*/
cwd?: string;

/**
* The initial configuration which will be used to set the defaults for the
* eventually returned configuration.
*/
config?: PartialDeep<Config>;

/**
* The extensions to support.
*
* @default ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs']
*/
extensions?: SupportedExtensions[];

/**
* The same level configuration directories which should also be searched.
*
* The order of the directories determines the priority. By default
* `.config/name.config.js` is preferred to `name.config.ts`.
*
* @default ['.config', '']
*/
dirs?: string[];

/**
* If your configuration object supports being called with an argument, this
* is used to generate the argument.
*
* It takes the options passed to `loadEsmConfig` and returns your desired
* configuration.
*
* @default () => {}
*/
getArgument?: (options: LoadEsmConfigOptions<Config>) => Argument;

/**
* Overwrite the way certain properties are merged.
*
* @see https://github.com/TehShrike/deepmerge#custommerge
*/
mergeOptions?: DeepMergeOptions;

/**
* By default the configuration file is searched from the provided working
* directory, but if not found, each parent directory will also be searched,
* all the way to the root directory.
*
* If this behaviour is not desired, set this to `false`.
*
* @default false
*/
disableUpwardLookup?: boolean;
}

/**
* @template Config - the type of configuration that will be loaded.
*/
export interface LoadEsmConfigResult<Config extends object = any> {
/**
* The absolute path to the resolved configuration file.
*/
path: string;

/**
* The configuration object that was loaded.
*/
config: Config;

/**
* All the dependencies encountered while loading the file.
*/
dependencies: string[];
}

export interface LoadFromFile<Argument = unknown>
extends Pick<
Required<LoadEsmConfigOptions<any, Argument>>,
'name' | 'cwd' | 'dirs' | 'extensions' | 'disableUpwardLookup'
> {
argument: Argument;
}

export interface BundleConfigFile {
fileName: string;
isEsModule?: boolean;
}

export interface GenerateLookupFiles {
name: string;
extensions: SupportedExtensions[];
dirs: string[];
}

0 comments on commit 04ba7ca

Please sign in to comment.