From 24864cfc9ad2bd6f0d1e195d19576a086c95875c Mon Sep 17 00:00:00 2001 From: productdevbook Date: Sun, 9 Feb 2025 23:37:51 +0300 Subject: [PATCH 1/5] feat: enhance transform and evalModule to support async callbacks --- lib/types.d.ts | 4 ++-- src/eval.ts | 15 ++++++++------ src/jiti.ts | 16 +++++++-------- src/transform.ts | 53 +++++++++++++++++++++++++++++++----------------- src/types.ts | 9 ++++++++ 5 files changed, 61 insertions(+), 36 deletions(-) diff --git a/lib/types.d.ts b/lib/types.d.ts index 4efb2b56..ae2fde81 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -35,12 +35,12 @@ export interface Jiti extends NodeRequire { /** * Transform source code */ - transform: (opts: TransformOptions) => string; + transform: (opts: TransformOptions, cb?: (source: string) => string) => Promise; /** * Evaluate transformed code as a module */ - evalModule: (source: string, options?: EvalModuleOptions) => unknown; + evalModule: (source: string, options?: EvalModuleOptions, cb?: (source: string, filename: string) => Promise | string) => unknown; } /** diff --git a/src/eval.ts b/src/eval.ts index 9cd80dd4..de776f9e 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -15,11 +15,12 @@ import { jitiRequire, nativeImportOrRequire } from "./require"; import createJiti from "./jiti"; import { transform } from "./transform"; -export function evalModule( +export async function evalModule( ctx: Context, source: string, evalOptions: EvalModuleOptions = {}, -): any { + cb?: (source: string, filename: string) => Promise | string, +): Promise { // Resolve options const id = evalOptions.id || @@ -45,14 +46,15 @@ export function evalModule( (isTypescript || isESM || ctx.isTransformRe.test(filename) || hasESMSyntax(source))); const start = performance.now(); if (needsTranspile) { - source = transform(ctx, { + source = await transform(ctx, { filename, source, ts: isTypescript, async: evalOptions.async ?? false, jsx: ctx.opts.jsx, - }); + }, cb); const time = Math.round((performance.now() - start) * 1000) / 1000; + console.log('filename' , filename) debug( ctx, "[transpile]", @@ -85,13 +87,13 @@ export function evalModule( } catch (error: any) { debug(ctx, "Native require error:", error); debug(ctx, "[fallback]", filename); - source = transform(ctx, { + source = await transform(ctx, { filename, source, ts: isTypescript, async: evalOptions.async ?? false, jsx: ctx.opts.jsx, - }); + }, cb); } } } @@ -118,6 +120,7 @@ export function evalModule( nativeImport: ctx.nativeImport, onError: ctx.onError, createRequire: ctx.createRequire, + callbackStore: ctx.callbackStore, // Pass the callback store }, true /* isNested */, ); diff --git a/src/jiti.ts b/src/jiti.ts index b4d1103d..82a69879 100644 --- a/src/jiti.ts +++ b/src/jiti.ts @@ -5,6 +5,7 @@ import type { Context, EvalModuleOptions, JitiResolveOptions, + SourceTransformer, } from "./types"; import { platform } from "node:os"; import { pathToFileURL } from "mlly"; @@ -27,11 +28,7 @@ export default function createJiti( userOptions: JitiOptions = {}, parentContext: Pick< Context, - | "parentModule" - | "parentCache" - | "nativeImport" - | "onError" - | "createRequire" + "parentModule" | "parentCache" | "nativeImport" | "onError" | "createRequire" | "callbackStore" >, isNested = false, ): Jiti { @@ -96,6 +93,7 @@ export default function createJiti( parentCache: parentContext.parentCache, nativeImport: parentContext.nativeImport, createRequire: parentContext.createRequire, + callbackStore: parentContext.callbackStore, }; // Debug @@ -135,11 +133,11 @@ export default function createJiti( paths: nativeRequire.resolve.paths, }, ), - transform(opts: TransformOptions) { - return transform(ctx, opts); + transform(opts: TransformOptions, sourceTransformer?: SourceTransformer) { + return transform(ctx, opts, sourceTransformer); }, - evalModule(source: string, options?: EvalModuleOptions) { - return evalModule(ctx, source, options); + evalModule(source: string, options?: EvalModuleOptions, sourceTransformer?: SourceTransformer) { + return evalModule(ctx, source, options, sourceTransformer); }, async import( id: string, diff --git a/src/transform.ts b/src/transform.ts index 78509319..72be1aa4 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,30 +1,45 @@ -import type { Context, TransformOptions } from "./types"; +import type { Context, TransformOptions, SourceTransformer } from "./types"; import { getCache } from "./cache"; import { debug } from "./utils"; -export function transform(ctx: Context, topts: TransformOptions): string { - let code = getCache(ctx, topts, () => { - const res = ctx.opts.transform!({ +export async function transform(ctx: Context, topts: TransformOptions, sourceTransformer?: SourceTransformer): Promise { + if (!topts.filename) { + throw new Error("transform: filename is required"); + } + + // Initialize or update callback store + ctx.callbackStore ??= new Map(); + if (sourceTransformer) { + ctx.callbackStore.set('sourceTransformer', sourceTransformer); + } + + let source = topts.source; + const globalTransformer = ctx.callbackStore?.get('sourceTransformer'); + + + if (globalTransformer) { + try { + source = await globalTransformer(source, topts.filename!); + } catch (error_) { + debug(ctx, 'Source transformer error:', error_); + } + } + + const code = getCache(ctx, topts, () => { + return ctx.opts.transform!({ ...ctx.opts.transformOptions, babel: { - ...(ctx.opts.sourceMaps - ? { - sourceFileName: topts.filename, - sourceMaps: "inline", - } - : {}), + ...(ctx.opts.sourceMaps ? { + sourceFileName: topts.filename, + sourceMaps: "inline", + } : {}), ...ctx.opts.transformOptions?.babel, }, interopDefault: ctx.opts.interopDefault, ...topts, - }); - if (res.error && ctx.opts.debug) { - debug(ctx, res.error); - } - return res.code; + source, + }).code; }); - if (code.startsWith("#!")) { - code = "// " + code; - } - return code; + + return code.startsWith("#!") ? "// " + code : code; } diff --git a/src/types.ts b/src/types.ts index be7faec7..f948e869 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,4 +25,13 @@ export interface Context { additionalExts: string[]; nativeRequire: NodeRequire; createRequire: (typeof import("node:module"))["createRequire"]; + callbackStore?: Map; +} + +export type SourceTransformer = (source: string, filename: string) => Promise | string; + +export interface CacheOptions { + key: string; + invalidate?: boolean; + transform?: () => string | Promise; } From 3245adfa48dc50a439ffc9da824a9747127797cb Mon Sep 17 00:00:00 2001 From: productdevbook Date: Mon, 10 Feb 2025 08:10:55 +0300 Subject: [PATCH 2/5] feat: update transform and evalModule to use sourceTransformer callback --- lib/types.d.ts | 4 ++-- src/eval.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/types.d.ts b/lib/types.d.ts index ae2fde81..13ee2919 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -35,12 +35,12 @@ export interface Jiti extends NodeRequire { /** * Transform source code */ - transform: (opts: TransformOptions, cb?: (source: string) => string) => Promise; + transform: (opts: TransformOptions, sourceTransformer?: (source: string) => string) => Promise; /** * Evaluate transformed code as a module */ - evalModule: (source: string, options?: EvalModuleOptions, cb?: (source: string, filename: string) => Promise | string) => unknown; + evalModule: (source: string, options?: EvalModuleOptions, sourceTransformer?: (source: string, filename: string) => Promise | string) => unknown; } /** diff --git a/src/eval.ts b/src/eval.ts index de776f9e..d3e10951 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -19,7 +19,7 @@ export async function evalModule( ctx: Context, source: string, evalOptions: EvalModuleOptions = {}, - cb?: (source: string, filename: string) => Promise | string, + sourceTransformer?: (source: string, filename: string) => Promise | string, ): Promise { // Resolve options const id = @@ -52,7 +52,7 @@ export async function evalModule( ts: isTypescript, async: evalOptions.async ?? false, jsx: ctx.opts.jsx, - }, cb); + }, sourceTransformer); const time = Math.round((performance.now() - start) * 1000) / 1000; console.log('filename' , filename) debug( @@ -93,7 +93,7 @@ export async function evalModule( ts: isTypescript, async: evalOptions.async ?? false, jsx: ctx.opts.jsx, - }, cb); + }, sourceTransformer); } } } From c42f581047a2d4516ced2a371429f3d7e0dafcc4 Mon Sep 17 00:00:00 2001 From: productdevbook Date: Mon, 10 Feb 2025 08:14:59 +0300 Subject: [PATCH 3/5] chore: lint --- lib/types.d.ts | 14 ++++++++++++-- src/eval.ts | 43 +++++++++++++++++++++++++++---------------- src/jiti.ts | 13 +++++++++++-- src/transform.ts | 25 +++++++++++++++---------- src/types.ts | 5 ++++- 5 files changed, 69 insertions(+), 31 deletions(-) diff --git a/lib/types.d.ts b/lib/types.d.ts index 13ee2919..1a1bd12c 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -35,12 +35,22 @@ export interface Jiti extends NodeRequire { /** * Transform source code */ - transform: (opts: TransformOptions, sourceTransformer?: (source: string) => string) => Promise; + transform: ( + opts: TransformOptions, + sourceTransformer?: (source: string) => string, + ) => Promise; /** * Evaluate transformed code as a module */ - evalModule: (source: string, options?: EvalModuleOptions, sourceTransformer?: (source: string, filename: string) => Promise | string) => unknown; + evalModule: ( + source: string, + options?: EvalModuleOptions, + sourceTransformer?: ( + source: string, + filename: string, + ) => Promise | string, + ) => unknown; } /** diff --git a/src/eval.ts b/src/eval.ts index d3e10951..a37325c0 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -19,7 +19,10 @@ export async function evalModule( ctx: Context, source: string, evalOptions: EvalModuleOptions = {}, - sourceTransformer?: (source: string, filename: string) => Promise | string, + sourceTransformer?: ( + source: string, + filename: string, + ) => Promise | string, ): Promise { // Resolve options const id = @@ -46,15 +49,19 @@ export async function evalModule( (isTypescript || isESM || ctx.isTransformRe.test(filename) || hasESMSyntax(source))); const start = performance.now(); if (needsTranspile) { - source = await transform(ctx, { - filename, - source, - ts: isTypescript, - async: evalOptions.async ?? false, - jsx: ctx.opts.jsx, - }, sourceTransformer); + source = await transform( + ctx, + { + filename, + source, + ts: isTypescript, + async: evalOptions.async ?? false, + jsx: ctx.opts.jsx, + }, + sourceTransformer, + ); const time = Math.round((performance.now() - start) * 1000) / 1000; - console.log('filename' , filename) + console.log("filename", filename); debug( ctx, "[transpile]", @@ -87,13 +94,17 @@ export async function evalModule( } catch (error: any) { debug(ctx, "Native require error:", error); debug(ctx, "[fallback]", filename); - source = await transform(ctx, { - filename, - source, - ts: isTypescript, - async: evalOptions.async ?? false, - jsx: ctx.opts.jsx, - }, sourceTransformer); + source = await transform( + ctx, + { + filename, + source, + ts: isTypescript, + async: evalOptions.async ?? false, + jsx: ctx.opts.jsx, + }, + sourceTransformer, + ); } } } diff --git a/src/jiti.ts b/src/jiti.ts index 82a69879..7238faec 100644 --- a/src/jiti.ts +++ b/src/jiti.ts @@ -28,7 +28,12 @@ export default function createJiti( userOptions: JitiOptions = {}, parentContext: Pick< Context, - "parentModule" | "parentCache" | "nativeImport" | "onError" | "createRequire" | "callbackStore" + | "parentModule" + | "parentCache" + | "nativeImport" + | "onError" + | "createRequire" + | "callbackStore" >, isNested = false, ): Jiti { @@ -136,7 +141,11 @@ export default function createJiti( transform(opts: TransformOptions, sourceTransformer?: SourceTransformer) { return transform(ctx, opts, sourceTransformer); }, - evalModule(source: string, options?: EvalModuleOptions, sourceTransformer?: SourceTransformer) { + evalModule( + source: string, + options?: EvalModuleOptions, + sourceTransformer?: SourceTransformer, + ) { return evalModule(ctx, source, options, sourceTransformer); }, async import( diff --git a/src/transform.ts b/src/transform.ts index 72be1aa4..f5457fc7 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -2,7 +2,11 @@ import type { Context, TransformOptions, SourceTransformer } from "./types"; import { getCache } from "./cache"; import { debug } from "./utils"; -export async function transform(ctx: Context, topts: TransformOptions, sourceTransformer?: SourceTransformer): Promise { +export async function transform( + ctx: Context, + topts: TransformOptions, + sourceTransformer?: SourceTransformer, +): Promise { if (!topts.filename) { throw new Error("transform: filename is required"); } @@ -10,18 +14,17 @@ export async function transform(ctx: Context, topts: TransformOptions, sourceTra // Initialize or update callback store ctx.callbackStore ??= new Map(); if (sourceTransformer) { - ctx.callbackStore.set('sourceTransformer', sourceTransformer); + ctx.callbackStore.set("sourceTransformer", sourceTransformer); } let source = topts.source; - const globalTransformer = ctx.callbackStore?.get('sourceTransformer'); - + const globalTransformer = ctx.callbackStore?.get("sourceTransformer"); if (globalTransformer) { try { source = await globalTransformer(source, topts.filename!); } catch (error_) { - debug(ctx, 'Source transformer error:', error_); + debug(ctx, "Source transformer error:", error_); } } @@ -29,10 +32,12 @@ export async function transform(ctx: Context, topts: TransformOptions, sourceTra return ctx.opts.transform!({ ...ctx.opts.transformOptions, babel: { - ...(ctx.opts.sourceMaps ? { - sourceFileName: topts.filename, - sourceMaps: "inline", - } : {}), + ...(ctx.opts.sourceMaps + ? { + sourceFileName: topts.filename, + sourceMaps: "inline", + } + : {}), ...ctx.opts.transformOptions?.babel, }, interopDefault: ctx.opts.interopDefault, @@ -40,6 +45,6 @@ export async function transform(ctx: Context, topts: TransformOptions, sourceTra source, }).code; }); - + return code.startsWith("#!") ? "// " + code : code; } diff --git a/src/types.ts b/src/types.ts index f948e869..52167ca9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -28,7 +28,10 @@ export interface Context { callbackStore?: Map; } -export type SourceTransformer = (source: string, filename: string) => Promise | string; +export type SourceTransformer = ( + source: string, + filename: string, +) => Promise | string; export interface CacheOptions { key: string; From 9e500dbe278c5d10484000e389cf672c29daa5b5 Mon Sep 17 00:00:00 2001 From: productdevbook Date: Mon, 10 Feb 2025 08:19:55 +0300 Subject: [PATCH 4/5] feat: enhance transform function to handle errors and adjust code formatting --- src/transform.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/transform.ts b/src/transform.ts index f5457fc7..4e3b67e6 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -28,8 +28,8 @@ export async function transform( } } - const code = getCache(ctx, topts, () => { - return ctx.opts.transform!({ + let code = getCache(ctx, topts, () => { + const res = ctx.opts.transform!({ ...ctx.opts.transformOptions, babel: { ...(ctx.opts.sourceMaps @@ -43,8 +43,14 @@ export async function transform( interopDefault: ctx.opts.interopDefault, ...topts, source, - }).code; + }); + if (res.error && ctx.opts.debug) { + debug(ctx, res.error); + } + return res.code; }); - - return code.startsWith("#!") ? "// " + code : code; + if (code.startsWith("#!")) { + code = "// " + code; + } + return code; } From 6da9265fb20ed1f13f6602fd73a36d6de390ad8a Mon Sep 17 00:00:00 2001 From: productdevbook Date: Mon, 10 Feb 2025 08:36:13 +0300 Subject: [PATCH 5/5] feat: update getCache and transform functions to support async operations and improve error handling --- src/cache.ts | 8 ++++---- src/eval.ts | 1 - src/transform.ts | 34 +++++++++++++++++----------------- src/types.ts | 2 +- test/fixtures.test.ts | 2 ++ 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index a8204775..cb14d556 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -7,11 +7,11 @@ import { debug, isWritable, md5 } from "./utils"; const CACHE_VERSION = "9"; -export function getCache( +export async function getCache( ctx: Context, topts: TransformOptions, - get: () => string, -): string { + get: () => Promise | string, +): Promise { if (!ctx.opts.fsCache || !topts.filename) { return get(); } @@ -41,7 +41,7 @@ export function getCache( } debug(ctx, "[cache]", "[miss]", topts.filename); - const result = get(); + const result = await Promise.resolve(get()); if (!result.includes("__JITI_ERROR__")) { writeFileSync(cacheFilePath, result + sourceHash, "utf8"); diff --git a/src/eval.ts b/src/eval.ts index a37325c0..2b848d4f 100644 --- a/src/eval.ts +++ b/src/eval.ts @@ -61,7 +61,6 @@ export async function evalModule( sourceTransformer, ); const time = Math.round((performance.now() - start) * 1000) / 1000; - console.log("filename", filename); debug( ctx, "[transpile]", diff --git a/src/transform.ts b/src/transform.ts index 4e3b67e6..ef1b08bc 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -11,46 +11,46 @@ export async function transform( throw new Error("transform: filename is required"); } - // Initialize or update callback store + const filename = topts.filename; + ctx.callbackStore ??= new Map(); if (sourceTransformer) { ctx.callbackStore.set("sourceTransformer", sourceTransformer); } - let source = topts.source; - const globalTransformer = ctx.callbackStore?.get("sourceTransformer"); + return await getCache(ctx, topts, async () => { + let source = topts.source; + const transformer = ctx.callbackStore?.get("sourceTransformer"); - if (globalTransformer) { - try { - source = await globalTransformer(source, topts.filename!); - } catch (error_) { - debug(ctx, "Source transformer error:", error_); + if (transformer) { + try { + source = await Promise.resolve(transformer(source, filename)); + } catch (error_) { + debug(ctx, "Source transformer error:", error_); + } } - } - let code = getCache(ctx, topts, () => { const res = ctx.opts.transform!({ + ...topts, ...ctx.opts.transformOptions, babel: { ...(ctx.opts.sourceMaps ? { - sourceFileName: topts.filename, + sourceFileName: filename, sourceMaps: "inline", } : {}), ...ctx.opts.transformOptions?.babel, }, interopDefault: ctx.opts.interopDefault, - ...topts, source, }); + if (res.error && ctx.opts.debug) { debug(ctx, res.error); } - return res.code; + + const code = res.code; + return code.startsWith("#!") ? "// " + code : code; }); - if (code.startsWith("#!")) { - code = "// " + code; - } - return code; } diff --git a/src/types.ts b/src/types.ts index 52167ca9..d139809a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,5 +36,5 @@ export type SourceTransformer = ( export interface CacheOptions { key: string; invalidate?: boolean; - transform?: () => string | Promise; + transform?: () => Promise | string; } diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts index 07652f61..330c9065 100644 --- a/test/fixtures.test.ts +++ b/test/fixtures.test.ts @@ -49,6 +49,8 @@ describe("fixtures", async () => { .replace(" ^", " ^") // eslint-disable-next-line no-control-regex .replace(/\u001B\[[\d;]*m/gu, "") + .replace(/^filename.*$/gm, "") // Remove filename lines + .replace(/\n+/g, "\n") // Remove extra newlines .trim() ); }