/** @file Civet ESM loader. In Node 20.6.0+, use Civet's `register` to install this ESM loader: @example ```bash node --import @danielx/civet/register source.civet ``` In older Node, use `--loader` to install this ESM loader: @example ```bash node --loader @danielx/civet/esm source.civet ``` Previously depended on ts-node esm loader being downstream: @example ```bash node --loader ts-node/esm --loader @danielx/civet/esm source.civet ``` If you want to configure this ESM loader, you can make your own `register.mjs` along these lines: @example ```javascript import { register } from 'node:module'; register('@danielx/civet/esm', import.meta.url, {data: { parseOptions: { // Add your parse options here }, }}); ``` */ { readFile } from fs/promises { dirname } from path { pathToFileURL, fileURLToPath } from url { compile, SourceMap } from ./main.civet { findConfig, loadConfig } from ./config.civet type { ParseOptions } from @danielx/civet export type RegisterOptions /** config filename, or false/null to not look for default config file */ config?: string | false | null parseOptions?: ParseOptions baseURL := pathToFileURL(process.cwd() + '/').href extensionsRegex := /\.civet$/ globalOptions: RegisterOptions .= {} export function initialize(options: RegisterOptions = {}) globalOptions = options export function resolve(specifier: string, context: any, next: any) { parentURL = baseURL } := context if extensionsRegex.test specifier return shortCircuit: true format: "civet" url: new URL(specifier, parentURL).href // Let Node.js handle all other specifiers. return next specifier, context export async function load(url: string, context: any, next: any) if context.format is "civet" path := fileURLToPath url source := await readFile path let loadedConfig: RegisterOptions? { config } .= globalOptions if config is undefined config = await findConfig dirname path if config loadedConfig = await loadConfig config options: Parameters[1] & { sourceMap: true } := filename: path sourceMap: true js: true if globalOptions.parseOptions options.parseOptions = globalOptions.parseOptions if loadedConfig?.parseOptions options.parseOptions = {...options.parseOptions, ...loadedConfig.parseOptions} let tsSource, sourceMap try {code: tsSource, sourceMap} = await compile source, options catch e console.error `Civet failed to compile ${url}:`, e throw e // NOTE: Append .tsx to URL so ts-node treats as TypeScript transpiledUrl := url + ".tsx" // NOTE: Assuming ts-node hook follows load hook result := await next transpiledUrl, // ts-node won't transpile unless this is module // can't use commonjs since we don't rewrite imports format: "module" // NOTE: Setting the source in the context makes it available when ts-node uses defaultLoad source: tsSource // Remove .tsx extension for final URL result.responseURL = (result.responseURL ?? transpiledUrl) .replace /.tsx$/, '' // parse source map from downstream (ts-node) result // compose with civet source map result.source = SourceMap.remap(result.source, sourceMap, url, result.responseURL) return result // Other URLs continue unchanged. return next url, context