Getting Started

Everything ships in one package, oxc-tsrx. It gives you the oxlint and oxfmt commands you already know, now handling .tsrx files, plus a parser API, a language server, and support for your own custom JavaScript lint plugins.

It does not compile anything. Building and running .tsrx is your framework's TSRX plugin's job, and you install that separately from tsrx.dev/getting-started (opens in new tab). The two are independent: this package never touches your build or dev server.

Install#

You need Node.js 20.19 or newer. Install one dev dependency:

npm install --save-dev oxc-tsrx@latest

That is the whole setup. The tools are Rust, but you get a prebuilt binary for your platform: no Rust needed, no install scripts, nothing fetched later. It works on CI that blocks postinstall.

Eight platforms have binaries. Platform Support says which is yours and how well tested it is.

The minimum steps, per host#

This is the complete list of things you have to run to lint and format .tsrx.

Where you use it Steps What you run
Command line (oxlint, oxfmt) 1 npm install --save-dev oxc-tsrx@latest
Editor, through the released official OXC extension 1 the same install, and nothing else
Vite+ (vp lint, vp fmt) 2 the same install, then oxc-tsrx setup

No row asks you to create a config file, add an ignore file, or add a lifecycle script. oxc-tsrx writes nothing into your project during install.

Making TSRX a language in your editor belongs to the TSRX toolchain, not to this package. It needs @tsrx/typescript-plugin, a framework binding, and a plugins entry in the tsconfig that owns your source. setup reports all three and installs none. Custom JavaScript plugins walks the sequence on a fresh scaffold.

What the install adds to node_modules/.bin#

Three commands are yours to type:

Command What it is
oxlint the linter. Handles .tsrx plus ordinary files
oxfmt the formatter. Same split
oxc-tsrx providers, status, setup, and remove. See the CLI reference

Four more get linked that you never type: three native leaf commands, plus tsgolint from a dependency.

  • Not Node-only. npm, pnpm, yarn, and bun are covered in CI, and Deno works but is not. Only the wrappers need Node; the linter and formatter are one standalone binary at node_modules/@oxc-tsrx/native-<your-platform>/bin/oxc-tsrx.
  • Except under Vite+, where oxlint and oxfmt are Vite+'s wrappers rather than ours. Use vp lint and vp fmt.

To see what a host finds in your project, without changing anything:

npx oxc-tsrx providers --json

The line to look for is routed extensions: .tsrx -> oxc-tsrx. (Don't be alarmed if npx oxc-tsrx status prints missing three times outside a Vite+ project. That is the correct result, and Limitations says why.)

Editors need no extra install#

Install oxc-tsrx and nothing else, and the released official OXC extension gives you .tsrx diagnostics that refresh as you type, formatting, quick fixes you can apply, and your own Oxlint JavaScript plugin rules. Ordinary TypeScript files keep going to canonical Oxlint.

One step is not an install, and skipping it looks like a broken integration. The official extension never activates on .tsrx. Open any JavaScript, TypeScript, or JSON file once, and .tsrx is served for the rest of the session. See the editor page for what that session covered.

Using Vite+? (compatibility step)#

Released Vite+ finds its lint and format tools through project-local packages named literally oxlint and oxfmt. A command name cannot satisfy that, and this project cannot legitimately publish a package under either name, so Vite+ needs the project-local slots that oxc-tsrx setup writes:

npm install --save-dev vite-plus oxc-tsrx@latest
npx oxc-tsrx setup

Pick your own package manager in the tabs above and run both lines with it. In a project vp create scaffolded, that is almost never the npm tab: vp create writes a devEngines.packageManager block into package.json naming pnpm, and npm then refuses to run there at all.

setup is explicit, idempotent, reversible, and never edits package.json. It works inside node_modules, so run it again after every clean dependency install.

After that, vp lint, vp fmt, and vp check --fix handle .tsrx. If your scaffold turns type-aware lint on, add one more dependency first: Vite+ and this package can disagree about which oxlint-tsgolint runs. The type-aware template default has the failure, the fix, and the alternative.

Vite+ is the only place an install alone is not enough.

Create a TSRX file#

Save this as src/Cart.tsrx. On this site, the "Try in playground" button under the snippet lets you explore it in your browser without installing anything. The var total and debugger lines are there on purpose: they give the linter something to catch.

export function Cart({ items }: Props) @{
  var total = 0;
  debugger;

  <section class="cart">
    @if (items.length > 0) {
      @for (const item of items; key item.id) {
        <Row item={item} />
      }
    } @else {
      <Empty />
    }
  </section>
}

Lint and format it#

Run the linter, then ask the formatter which files would change. This is a recording of both commands running against this exact file:

See it run
npx oxlint src/Cart.tsrx
src/Cart.tsrx:2:7: warning eslint(no-unused-vars): Variable 'total' is declared but never used. Unused variables should start with a '_'.
src/Cart.tsrx:3:3: warning eslint(no-debugger): `debugger` statement is not allowed
Found 0 error(s) and 2 warning(s).


npx oxfmt --check src/Cart.tsrx
Checking formatting...

src/Cart.tsrx (0ms)

Format issues found in above 1 files. Run without `--check` to fix.
Finished in 0ms on 1 files using 18 threads.
This output was captured from the real native binaries at build time, so it matches what they actually returned.

Every diagnostic points at line and column numbers in your original TSRX code, never at a transformed copy. Once you have fixed the warnings, let the formatter write its layout changes:

See it run
# The warnings are fixed, but look at the spacing
cat src/Cart.tsrx
export function Cart({items}:Props) @{
  <section   class="cart">
      @if (items.length>0) {
      @for (const item of items; key item.id) {
          <Row item={item}/>
      }
      } @else {
        <Empty/>
      }
  </section>
}


# Rewrite the file in place; a summary line and no file list means it worked
npx oxfmt --write src/Cart.tsrx
Finished in 0ms on 1 files using 18 threads.


# Same file, now in canonical Oxfmt layout
cat src/Cart.tsrx
export function Cart({ items }: Props) @{
  <section class="cart">
    @if (items.length > 0) {
      @for (const item of items; key item.id) {
        <Row item={item} />;
      }
    } @else {
      <Empty />;
    }
  </section>;
}
Real output, captured at build time. The sample is the src/Cart.tsrx from above with its two warnings fixed but sloppy spacing left behind.

Two things to know:

  • Mixed file types are fine. .tsrx files go through the TSRX engine, while ordinary .js/.ts/.tsx files go straight to OXC.
  • You can tune rule severity inline. For example npx oxlint --warn no-console --deny no-debugger src/Cart.tsrx.

Give the linter a path, or an empty folder will bury you#

npx oxlint with no path lints everything under the current directory, node_modules included. That is canonical Oxlint behavior, not something TSRX adds. Two things avoid it:

  • A .gitignore listing node_modules. Oxlint honors it even outside a git repository. Real projects have one; fresh scratch folders do not, which is the only place this bites.
  • Naming a path. npx oxlint src lints your sources and nothing else.

This package ships no ignore file and no config, and writes none. Your .gitignore and .oxlintrc.json are the only inputs.

Do not run npx oxlint --fix where node_modules is still in scope. With nothing narrowing the run, --fix rewrites files inside your dependency tree and still exits 0. Official Oxlint does the same, so this is upstream parity rather than a TSRX defect, but your dependency tree is modified either way. Fix a path you own (npx oxlint --fix src), or make sure node_modules is ignored first.

oxfmt is not affected. It skips node_modules unless you pass --with-node-modules.

Configuration#

Both commands find your normal OXC config by searching upward from the current directory (.oxlintrc.json/.oxlintrc.jsonc for lint, .oxfmtrc.json/.oxfmtrc.jsonc for format), or take an explicit --config/-c path. See Configuration for exactly which fields are supported.

Build from source (optional)#

If you would rather build the native binaries yourself, you need a stable Rust toolchain (rustup (opens in new tab)):

git clone https://github.com/markless-dev/oxc-tsrx.git
cd oxc-tsrx
cargo build --release --locked -p oxc_tsrx_cli --bins

Keep the --locked flag: it makes Cargo build against the exact pinned OXC commit from the lockfile. The binaries land in target/release/:

See it run
# The report is one line of JSON; jq picks out the diagnostics
target/release/oxc-tsrx --format=json src/Cart.tsrx | jq '.diagnostics'
[
  {
    "filename": "src/Cart.tsrx",
    "rule": "no-unused-vars",
    "code": "eslint(no-unused-vars)",
    "severity": "warning",
    "message": "Variable 'total' is declared but never used. Unused variables should start with a '_'.",
    "labels": [
      {
        "span": {
          "offset": 48,
          "length": 5
        },
        "message": "'total' is declared here"
      }
    ]
  },
  {
    "filename": "src/Cart.tsrx",
    "rule": "no-debugger",
    "code": "eslint(no-debugger)",
    "severity": "warning",
    "message": "`debugger` statement is not allowed",
    "labels": [
      {
        "span": {
          "offset": 61,
          "length": 9
        }
      }
    ]
  }
]


# And the metadata proves it parsed your file exactly once
target/release/oxc-tsrx --format=json src/Cart.tsrx | jq '.oxcTsrx.parseCount'
1
Real output, captured at build time, for the src/Cart.tsrx file above. The native binary prints the whole report as one line of JSON; jq makes it readable here.

The native binaries emit JSON diagnostics and want explicit file paths. The friendly text output, directory walking, and glob handling come from the npm commands, so most projects only need those. See the CLI Reference for every flag.

Next steps#