Files
oxc-project__oxc/napi/transform-relay/test/transform.test.ts
Boshen 1c4f519e36 feat(napi): add Relay transform plugin (#25503)
## What

Ports the Relay `graphql` tagged template transform (babel-plugin-relay / swc_relay) as an individual napi transform, per #24914:

- **`crates/oxc_relay`** — the core transform as its own crate (modeled on `oxc_react_compiler`): replaces `graphql` tagged templates with references to relay-compiler artifacts. Definition names are extracted textually like `swc_relay` (equivalent to `\b(fragment|mutation|query|subscription)\s+(\w+)` with `#` comments stripped; hand-rolled scanner, no regex dependency).
- **`napi/transform-relay`** (`oxc-transform-relay`) — standalone napi package copied from the `napi/transform-react` template: parse → semantic → relay traverse → codegen. Only `graphql` tags are rewritten; TypeScript/JSX syntax is preserved untouched, so the output composes with any downstream toolchain.

## Options

| option | default | behavior |
|---|---|---|
| `artifactDirectory` | unset | when set, the import path is computed lexically relative to the transformed file (babel-plugin-relay `getRelativeImportPath` semantics — swc emits absolute paths instead); unset → `./__generated__/` next to the file |
| `language` | `javascript` | `typescript` imports `Name.graphql.ts`, otherwise `Name.graphql.js` |
| `eagerEsModules` | `true` | hoisted default imports (babel-plugin-relay ≥ v17 parity); `false` emits inline `require()` (`@swc/plugin-relay` / Next.js behavior) |

A `graphql` tag with no extractable definition name produces an error diagnostic (babel throws; swc silently skips).

Not supported, documented in both READMEs: dev-mode artifact hash validation, `jsModuleFormat: "haste"`, `isDevVariableName`, swc's Next.js-specific `projects`/`pagesDir`, and GraphQL document validation.

closes #24914

---

Implemented with Claude Code.
2026-08-13 13:56:01 +00:00

51 lines
1.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { transform, transformSync } from "../index";
const fixture = "const data = graphql`query FooQuery { id }`;\n";
describe("transformSync", () => {
it("hoists an ES import by default", () => {
const result = transformSync("foo.js", fixture);
expect(result.errors).toEqual([]);
expect(result.code).toMatchInlineSnapshot(`
"import _FooQuery from "./__generated__/FooQuery.graphql.js";
const data = _FooQuery;
"
`);
});
it("supports Relay options", () => {
const result = transformSync("project/src/pages/Foo.tsx", fixture, {
artifactDirectory: "project/src/__generated__",
language: "typescript",
eagerEsModules: false,
});
expect(result.errors).toEqual([]);
expect(result.code).toContain('require("../__generated__/FooQuery.graphql.ts")');
});
it("reports transform and option errors", () => {
const unnamed = transformSync("foo.js", "const data = graphql`{ id }`;");
expect(unnamed.code).toBe("");
expect(unnamed.errors[0].message).toContain("named GraphQL");
// @ts-expect-error Testing runtime validation.
const invalidOption = transformSync("foo.js", fixture, { language: "elm" });
expect(invalidOption.code).toBe("");
expect(invalidOption.errors[0].message).toContain("language");
});
});
describe("transform", () => {
it("transforms asynchronously", async () => {
const result = await transform("foo.js", fixture, { eagerEsModules: false });
expect(result.errors).toEqual([]);
expect(result.code).toContain('require("./__generated__/FooQuery.graphql.js")');
});
});