Many popular node packages support writing configuration files in JS or TS, like webpack
, vite
. Now I'm also trying to create a package supporting JS and TS configuration file, which would be used as: my-package --config path/to/config.ts
.
I first tried using require
directly, which failed for TS (that's quite an obvious result, since no loader for TS is specified).
So I then tried using typescript
package to transpile the config file and using require-from-string
to load the module, which doesn't work either when the specified config file import some other modules.
So my current problem is: how to get the configuration module resolved under current context.
Note that this package is designed to work like webpack-cli
, being added to devDependencies
by other packages and used as a tool for development. So the current context refers to the package that installs this package.
Here are some relative posts I've looked through and tried (so don't propose a duplicate to these questions):
Asked
Active
Viewed 239 times
2

0x269
- 688
- 8
- 20
-
What's your bin script? is there like `node something.js`? – Dimava Nov 19 '22 at 20:44
-
@Dimava Yeah, bin script is defined in `package.json` as `{ "bin": { "some-command": "dist/some-command.js" } }` – 0x269 Nov 20 '22 at 06:33
-
then try installing `tsx` and setting command to `tsc dist/some-command.js` – Dimava Nov 20 '22 at 08:16
-
or even `tsx dist/some-command.ts` – Dimava Nov 20 '22 at 08:21
-
@Dimava I guess you didn't quite get my purpose. I need to support all configuration formats, including json, js and ts, not merely ts. – 0x269 Nov 20 '22 at 10:02
1 Answers
2
After probing into the source of webpack-cli
, I finally get this done with the help of rechoir
. Here's the code:
import interpret from "interpret";
import rechoir from "rechoir";
import AsyncFs from "fs/promises";
import Path from "path";
import { pathToFileURL } from "URL";
// My package is build with `webpack`, so I need the following to bypass its dynamic require constraint.
declare var __non_webpack_require__: ((id: string) => any) | undefined;
const dynamicRequire = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : require;
async function tryRequireThenImport(module: string): Promise<any> {
let result;
try {
result = dynamicRequire(module);
} catch (error: any) {
let importEsm: ((module: string) => Promise<{ default: any }>) | undefined;
try {
importEsm = new Function("id", "return import(id);") as any;
} catch (e) {
importEsm = undefined;
}
if (error.code === "ERR_REQUIRE_ESM" && importEsm) {
const urlForConfig = pathToFileURL(module).href;
result = (await importEsm(urlForConfig)).default;
return result;
}
throw error;
}
// For babel/typescript
if (result && typeof result === "object" && "default" in result)
result = result.default || {};
return result || {};
}
async function loadModule(path: string): Promise<any> {
const ext = Path.extname(path);
if (ext === ".json") {
const content = await AsyncFs.readFile(path, "utf-8");
return JSON.parse(content);
}
if (!Object.keys(interpret.jsVariants).includes(ext))
throw new Error(`Unsupported file type: ${ext}`);
// This is the key point that gets the configuration module loaded under current context
rechoir.prepare(interpret.jsVariants, path);
return await tryRequireThenImport(path);
}
The implementation is mainly borrowed from webpack-cli
. Here are the sources:

0x269
- 688
- 8
- 20