@vis.gl/dev-tools
Version:
Dev tools for vis.gl frameworks
100 lines (90 loc) • 3.05 kB
text/typescript
/**
* Support module alias in ESM mode by implementing Node.js custom module resolver
* tsconfig-paths does not work in ESM, see https://github.com/dividab/tsconfig-paths/issues/122
* Adapted from https://github.com/TypeStrong/ts-node/discussions/1450
*/
import path from 'path';
import fs from 'fs';
import {pathToFileURL} from 'url';
import {getValidPath, ocularRoot} from '../utils/utils.js';
/** Node.js resolve hook,
* https://nodejs.org/api/module.html#resolvespecifier-context-nextresolve
*/
type ResolveHook = (
specifier: string,
context: {
conditions?: unknown;
importAssertions?: unknown;
parentURL: string;
},
nextResolve: ResolveHook
) => Promise<{
url: string;
format?: 'builtin' | 'commonjs' | 'json' | 'module' | 'wasm';
shortCircuit?: boolean;
}>;
// Load alias from file
const pathJSON = fs.readFileSync(path.resolve(ocularRoot, '.alias.json'), 'utf-8');
const paths: Record<string, string> = JSON.parse(pathJSON);
const matchPath = createMatchPath(paths);
export const resolve: ResolveHook = (specifier, context, nextResolver) => {
const mappedSpecifier = matchPath(specifier);
if (mappedSpecifier) {
const fileUrl = pathToFileURL(mappedSpecifier);
if (mappedSpecifier.match(/(\/\*|\.jsx?|\.tsx?|\.cjs|\.json)$/)) {
specifier = fileUrl.pathname;
} else if (mappedSpecifier.includes('/dist/')) {
specifier = getValidPath(`${fileUrl}.js`, `${fileUrl}/index.js`) || fileUrl.toString();
} else {
specifier = fileUrl.toString();
}
}
// @ts-expect-error omitted arguments are populated by Node.js
return nextResolver(specifier);
};
/** Checks if a path matches aliased pattern.
* If so, returns mapped path, otherwise returns null
*/
type AliasTest = (specifier: string) => string | null;
/** Get alias mapping function from ocular config */
function createMatchPath(aliases: Record<string, string>): AliasTest {
const tests: AliasTest[] = [];
for (const key in aliases) {
const alias = aliases[key];
let testFunc: AliasTest;
if (key.includes('*')) {
const regex = new RegExp(`^${key.replace('*', '(.+)')}`);
testFunc = (specifier: string) => {
const match = specifier.match(regex);
if (match) {
return specifier.replace(match[0], alias.replace('*', match[1]));
}
return null;
};
} else {
let defaultEntry = alias;
if (!alias.match(/(\/\*|\.jsx?|\.tsx?|\.cjs)$/)) {
defaultEntry = getValidPath(`${alias}/index.ts`, `${alias}/index.js`) || defaultEntry;
}
testFunc = (specifier: string) => {
if (key === specifier) {
return defaultEntry;
}
if (specifier.startsWith(`${key}/`)) {
return `${alias}${specifier.slice(key.length)}`;
}
return null;
};
}
tests.push(testFunc);
}
return (specifier: string) => {
for (const test of tests) {
const result = test(specifier);
if (result) {
return result;
}
}
return null;
};
}