Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[How To] Use with tsx runner #148

Open
joliss opened this issue Mar 6, 2024 · 4 comments
Open

[How To] Use with tsx runner #148

joliss opened this issue Mar 6, 2024 · 4 comments

Comments

@joliss
Copy link

joliss commented Mar 6, 2024

I'm using tsx, a ts-node alternative for running .ts files with Node. Is there a some way to run ts-patch projects with tsx?

@joliss joliss changed the title [How To] Use with tsx [How To] Use with tsx runner Mar 6, 2024
@joliss joliss changed the title [How To] Use with tsx runner [How To] Use with tsx runner Mar 6, 2024
@cmidgley
Copy link

For those interested in tsx with ts-patch, I have it working with my transformers. The reason it doesn't "just work" is because tsx uses esbuild, which means it does not call the typescript compiler API's that ts-patch modifies.

The trick is to get it to load your transformer before tsx so the input to esbuild is the output from your transformer. To do this, you need to use the node cli and not tsx, since once it starts running it will go directly to esbuild and never call your transformer.

For example, with modern register-based transformers (and having previously used ts-patch install):

node --import yourTransformer --import tsx myFile.ts

or this for legacy loader-based transformers:

node --loader yourTransformer --import tsx myFile.ts

This will cause your transformer to load before tsx, allowing the files to be processed by it before sending on to tsx and esbuild.

Also, if you have custom tsconfig.json file, you can specify that with the environment variable TSX_CONFIG_PATH

TSX_CONFIG_PATH=tsconfig.different.json node --import yourTransformer --import tsx myFile.ts

I have this all working with node 22.3.0, ts-patch 3.2.1, typescript 5.5.3 and tsx 4.16.2.

@m0ppers
Copy link

m0ppers commented Jan 10, 2025

I am probably stupid but it doesn't work for me. after adding a simple console log into my transformer I can see that node --import @blaaaaa/fancy-transformer --import tsx scripts/execute-job.ts successfully loads my transformer. however the default function in that transformer which should patch my code is never being called?

what am I missing?

@cmidgley
Copy link

I don't recall the details well, as I just played around until I got it to work (I'm no expert in this). One that sticks out is your comment "however the default function (is not) being called". If the transformer exports a default function that likely means it's a "loader" based transformer. Perhaps try --loader (though I'll bet you already tried that). Register-based loaders I believe have named exports.

Given I use a register-based export, I don't know if this will help, but I figure more information is better than less?!

In my package.json I start with something like this (FYI, this is for the @wessberg/DI dependency injection tool.

node --import di-register-loader --import tsx src\\main.ts

The di-register-loader is a private package that has a single index.mjs file:

import path from 'path';
import { pathToFileURL } from 'node:url';
import { register } from 'node:module';

const loaderPath = path.resolve('node_modules/@wessberg/di-compiler/dist/esm/loader.js');
const loaderURL = pathToFileURL(loaderPath).href;
register(loaderURL, pathToFileURL('./'));

This registers the transformed loader. You can likely skip all the path munging - it's just to help make the path resolution work since @wessberg/di-compiler wouldn't work because the di-register-loader package doesn't have mappings to resolve @wessberg. You likely can use only the register call with a regular path.

Now my usage is based on register style transformer loader. See here for the source of the loader I'm using, but for reference here is it pulled together post-packing as a single file:

import TS__default from 'typescript';
import path from 'path';
import fs from 'fs/promises';
import urlModule from 'url';
import { resolveOptions, ALLOWED_EXTENSIONS, transform } from './common/common.js';

const transformOptions = resolveOptions(TS__default);
const load = async (url, context, nextLoad) => {
    var _a;
    if (ALLOWED_EXTENSIONS.has(path.extname(url))) {
        const fileName = urlModule.fileURLToPath(url);
        const rawSource = await fs.readFile(fileName, "utf-8");
        if (rawSource != null) {
            const { code: source } = transform(rawSource.toString(), fileName, {
                ...transformOptions,
                typescript: TS__default
            });
            return {
                format: (_a = context.format) !== null && _a !== void 0 ? _a : "module",
                shortCircuit: true,
                source
            };
        }
    }
    // Defer to the next hook in the chain.
    return nextLoad(url, context);
};

Not a lot to learn here, unless you need to write your own register loader. Maybe your transformer has a register-based loader that you can use? If not then perhaps the above might help you write one (assuming you can't get a loader-based transformer working with --loader or some other trick).

I wish I could be more prescriptive in fixing your problem, but I don't fully understand it either. Hope something here helps you find a solution. Good luck!

@m0ppers
Copy link

m0ppers commented Jan 15, 2025

ok I get it. this is much more complicated than anticipated 😱 I am transforming some macros and I just inject the macro calls as functions using a loader now. thanks for the details though!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants