Cannot use import.meta.url in _.context.ts
I wanted to load the contents of a JSON file so I put some code like this in my /api/routes/_.context.ts file, as I'm accustomed to in ES modules:
import {createRequire} from 'module';
const require = createRequire(import.meta.url);
const mockResponse = require('../mocks/response.json');
export class Context {
getResponse() {
return mockResponse;
}
}
When I run Counterfact with the usual arguments I get the following error after "Starting REPL, type .help for more info":
Starting REPL, type .help for more info
node:internal/process/promises:389
new UnhandledPromiseRejection(reason);
^
UnhandledPromiseRejection: This error originated either by throwing inside of an async function
without a catch block, or by rejecting a promise which was not handled with .catch(). The promise
rejected with the reason "Error: Line 15: Unexpected reserved word".
at throwUnhandledRejectionsMode (node:internal/process/promises:389:7)
at processPromiseRejections (node:internal/process/promises:470:17)
at process.processTicksAndRejections (node:internal/process/task_queues:96:32) {
code: 'ERR_UNHANDLED_REJECTION'
}
Node.js v22.3.0
The "unexpected reserved word" in question is the import in import.meta.url, which I have confirmed by removing all code except for console.log(import.meta).
However, when I run the _.context.ts file directly (with tsx and adding console.log(mockResponse) at the end), it runs without an error:
$ npx tsx api/routes/_.context.ts
{ hello: 'world' }
I think something is going wrong in Counterfact's module loader, but I'm not sure what.
In the meantime I'm using JSON.parse(fs.readFileSync(...)), which works fine, but is less than optimal, and there may be other uses of import.meta that are blocked by this issue.
Minimal reproduction
I've created a minimal reproduction of the issue here: jrunning/counterfact-repro.
I've played with the repo and am trying some alternate ways to load the file. This gets closer:
import { readFileSync } from "fs";
let data;
try {
const fileData = readFileSync("../../test.json", { encoding: "utf-8" });
console.log("fileData", fileData);
data = JSON.parse(readFileSync("../../test.json", { encoding: "utf-8" }));
} catch (error) {
console.error("parse error!", error);
data = {};
}
But that doesn't seem to find the proper JSON file so I'm still doing something wrong. But something like this might be an alternative to using require.
Ok, using an absolute path works. Probably a better way to help the compiled code in .cache find the desired json file.
I've discovered that require is already in scope in _.context.ts (even though it's an ES module—confusing) and while require('../../test.json') doesn't work (it looks for ../../test.json.cjs), require.resolve('../../test.json') does work, so I can do the following:
JSON.parse(readFileSync(require.resolve('../../test.json'), 'utf8'));
I've discovered that
requireis already in scope in_.context.ts(even though it's an ES module—confusing) and whilerequire('../../test.json')doesn't work (it looks for../../test.json.cjs),require.resolve('../../test.json')does work, so I can do the following:JSON.parse(readFileSync(require.resolve('../../test.json'), 'utf8'));
I guess since all the code transpiles down to cjs this is why require is available. Glad this use case works.
Nice solution, @jrunning!
I guess since all the code transpiles down to cjs this is why require is available.
Yep! Because there's no API to clear the module cache with ESM, and I wanted to be able to "hot reload" code, I had to compile everything down to CJS. It gets the job done but leaves us with some leaky abstractions like this one.
Would it help if we add a utility function to Counterfact for loading JSON files?
export class Context {
constructor({ readJson }) {
this.readJson = readJson;
}
getResponse() {
return this.readJson('../mocks/response.json');
}
}
Would it help if we add a utility function to Counterfact for loading JSON files?
It seems like loading data from JSON could be a common use case, but the only data point I have is my own.
I'm sure it's common enough, just gut-checking the API.