diff --git a/include/utils.h b/include/utils.h index f9be579..d432507 100644 --- a/include/utils.h +++ b/include/utils.h @@ -19,6 +19,7 @@ uint16_t double_to_half(double value); char hex_char(int v); char *resolve_js_file(const char *filename); +char *resolve_typescript_source_fallback(const char *filename); int hex_digit(char c); int is_typescript_file(const char *filename); diff --git a/src/esm/loader.c b/src/esm/loader.c index b25fd42..f1308e5 100644 --- a/src/esm/loader.c +++ b/src/esm/loader.c @@ -162,6 +162,21 @@ static char *esm_try_resolve_index_with_exts(const char *dir, const char *spec) return esm_try_resolve_with_exts(dir, idx, false); } +static char *esm_try_resolve_relative_typescript_source_fallback( + const char *dir, + const char *spec, + const char *base_path +) { + if (!is_typescript_file(base_path)) return NULL; + + char *ts_spec = resolve_typescript_source_fallback(spec); + if (!ts_spec) return NULL; + + char *resolved = esm_try_resolve(dir, ts_spec, ""); + free(ts_spec); + return resolved; +} + static ant_value_t esm_default_export_or_namespace(ant_t *js, ant_value_t ns) { ant_value_t default_val = js_get_slot(ns, SLOT_DEFAULT); return vtype(default_val) != T_UNDEF ? default_val : ns; @@ -631,10 +646,14 @@ static char *esm_resolve_relative_path(const char *specifier, const char *base_p const char *spec = specifier; if (strncmp(specifier, "./", 2) == 0) spec = specifier + 2; + bool has_ext = esm_has_extension(spec); - if ((result = esm_try_resolve(dir, spec, ""))) goto cleanup; - if (has_ext) goto cleanup; + + if (has_ext) { + result = esm_try_resolve_relative_typescript_source_fallback(dir, spec, base_path); + goto cleanup; + } char *base_ext = esm_get_extension(base_path); if (!base_ext) goto cleanup; diff --git a/src/utils.c b/src/utils.c index c6ee8a2..12b8113 100644 --- a/src/utils.c +++ b/src/utils.c @@ -252,6 +252,32 @@ char *resolve_js_file(const char *filename) { return NULL; } +char *resolve_typescript_source_fallback(const char *filename) { + if (!filename) return NULL; + + const char *mapped_ext = NULL; + size_t trim_len = 0; + + size_t len = strlen(filename); + if (len > 3 && strcmp(filename + len - 3, ".js") == 0) { + mapped_ext = ".ts"; + trim_len = 3; + } else if (len > 4 && strcmp(filename + len - 4, ".mjs") == 0) { + mapped_ext = ".mts"; + trim_len = 4; + } else if (len > 4 && strcmp(filename + len - 4, ".cjs") == 0) { + mapped_ext = ".cts"; + trim_len = 4; + } else return NULL; + + size_t mapped_len = strlen(mapped_ext); + char *mapped = try_oom(len - trim_len + mapped_len + 1); + memcpy(mapped, filename, len - trim_len); + memcpy(mapped + len - trim_len, mapped_ext, mapped_len + 1); + + return mapped; +} + typedef struct { const char *repl; size_t repl_len; size_t *ri; const char *matched; size_t matched_len; diff --git a/tests/test_ts_js_extension_resolution.cjs b/tests/test_ts_js_extension_resolution.cjs new file mode 100644 index 0000000..16e85bf --- /dev/null +++ b/tests/test_ts_js_extension_resolution.cjs @@ -0,0 +1,10 @@ +(async () => { + const mod = await import('./ts_js_extension_resolution_main.ts'); + if (mod.default !== 'ts-source-fallback-ok') { + throw new Error(`expected ts-source-fallback-ok, got ${String(mod.default)}`); + } + console.log('ok'); +})().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/tests/ts_js_extension_resolution_dep.ts b/tests/ts_js_extension_resolution_dep.ts new file mode 100644 index 0000000..28d0fdf --- /dev/null +++ b/tests/ts_js_extension_resolution_dep.ts @@ -0,0 +1 @@ +export const resolvedValue = 'ts-source-fallback-ok'; diff --git a/tests/ts_js_extension_resolution_main.ts b/tests/ts_js_extension_resolution_main.ts new file mode 100644 index 0000000..43ecd6b --- /dev/null +++ b/tests/ts_js_extension_resolution_main.ts @@ -0,0 +1,3 @@ +import { resolvedValue } from './ts_js_extension_resolution_dep.js'; + +export default resolvedValue;