Native ESM support, possibly requiring integration with ts-node
cspotcode opened this issue ยท 8 comments
In ts-node
we released experimental support for native ECMAScript modules. To do this we needed to implement a custom resolve()
loader hook. It is a copy-paste of node's built-in resolver, tweaked for our needs.
Is native ESM support on your radar? Do you have thoughts on how best to implement it for tsconfig-paths? I expect we will need to coordinate, since node only supports a single loader hook.
I see that the compiler itself can perform these resolutions via ts.resolveModuleName
. Do you use that internally, or do you do something else?
just posted a similar issue but I am not sure if this repo is still maintained...
@desmap you can be the one to do it!
You can also stop using path mappings in your project. Probably wouldn't be much work since it's some find-and-replace of the import statements.
What benefits do you hope to get from switching to native ESM? You'll be forcing your users to install a non-standard path mapping hook.
What benefits do you hope to get from switching to native ESM?
It'd def debatable but I'd like it, check yarnpkg/berry#638 (comment) and yarnpkg/berry#638 (comment) for examples
I had trouble getting module path mappings to work with ESM (using tsconfig-paths
' register
function -- pre-ESM I did it this way: #157 (comment)). What finally worked for me was using a custom loader (thanks for the tip/example @geigerzaehler TypeStrong/ts-node#1450 (comment))
To use the loader code below, pass it to node
via --loader
option, e.g. node --loader loader.js main.js
.
Custom loader code
// (loader.ts)
// Uses tsconfig.json and createMatchPath (from tsconfig-paths lib) to implement a custom loader
// (resolve function) that applies path mappings like `@src/foo` --> `/path/to/app/build/src/foo.js`
// See also https://github.com/TypeStrong/ts-node/discussions/1450#discussion-3563207
import { existsSync } from 'fs';
import { readFile } from 'fs/promises';
import { basename, dirname, resolve as pathResolve } from 'path';
import { fileURLToPath } from 'url';
import { createMatchPath } from 'tsconfig-paths';
const __dirname = dirname(fileURLToPath(import.meta.url));
async function getTSConfig() {
const maxDepth = 32; // arbitrary
let depth = 0;
let tsConfigFile = pathResolve(__dirname, 'tsconfig.json');
while(!existsSync(tsConfigFile)) {
tsConfigFile = pathResolve(dirname(tsConfigFile), '..', basename(tsConfigFile));
depth++;
if (depth > maxDepth) {
throw Error(`maxDepth (${maxDepth}) exceeded while searching for tsconfig.json`);
}
}
return JSON.parse(await readFile(tsConfigFile, 'utf-8'));
}
const getMatchPathPromise = (async () => {
const tsConfig = await getTSConfig();
const baseUrl = tsConfig.compilerOptions.baseUrl || '.';
const outDir = tsConfig.compilerOptions.outDir || '.';
const absoluteBaseUrl = pathResolve(baseUrl, outDir);
const paths = tsConfig.compilerOptions.paths;
return createMatchPath(absoluteBaseUrl, paths);
})();
export async function resolve(specifier, context, defaultResolve) {
const matchPath = await getMatchPathPromise;
const mappedSpecifier = matchPath(specifier)
if (mappedSpecifier) {
specifier = `${mappedSpecifier}.js`
}
return defaultResolve(specifier, context, defaultResolve);
}
@jacobq Also have a look at this solution TypeStrong/ts-node#1450
It's rather simple because it extends native ts-node/esm loader.
@jacobq Also have a look at this solution TypeStrong/ts-node#1450 It's rather simple because it extends native ts-node/esm loader.
Yes, I saw it (hence why I mentioned it in my comment ๐). I probably should've mentioned that I am not actually using ts-node
though.
I wanted to mention new solution created by @charles-allen 4 days ago (TypeStrong/ts-node#1450 (comment))
Closing in favor of TypeStrong/ts-node#1585 which will add native path mapping support to ts-node.