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

Add support for "react-jsx" and jsxImportSource #139

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion js/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,17 @@ export interface CompilerOptions {
inlineSources?: boolean;
/** Controls how JSX constructs are emitted in JavaScript files. This only
* affects output of JS files that started in `.jsx` or `.tsx` files. */
jsx?: "jsx" | "preserve";
jsx?: "preserve" | "react-jsx" | "react-jsxdev" | "react-native" | "react";
/** Changes the function called in `.js` files when compiling JSX Elements
* using the classic JSX runtime. The most common change is to use `"h"` or
* `"preact.h"`. */
jsxFactory?: string;
/** Specify the JSX fragment factory function to use when targeting react JSX
* emit with jsxFactory compiler option is specified, e.g. `Fragment`. */
jsxFragmentFactory?: string;
/** The string module specifier to implicitly import JSX factories from when
* transpiling JSX. */
jsxImportSource?: string;
/** Enables the generation of sourcemap files. */
sourceMap?: boolean;
}
Expand Down
1 change: 1 addition & 0 deletions testdata/jsx/in.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const helloWorld = <div id="helloWorld"></div>;
1 change: 1 addition & 0 deletions testdata/jsx/out_no_transforms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const helloWorld = <div id="helloWorld"></div>;
3 changes: 3 additions & 0 deletions testdata/jsx/out_react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const helloWorld = /*#__PURE__*/ React.createElement("div", {
id: "helloWorld"
});
4 changes: 4 additions & 0 deletions testdata/jsx/out_react_jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { jsx as _jsx } from "/jsx-runtime";
const helloWorld = /*#__PURE__*/ _jsx("div", {
id: "helloWorld"
});
4 changes: 4 additions & 0 deletions testdata/jsx/out_react_jsx_custom_import_source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { jsx as _jsx } from "example/jsx-runtime";
const helloWorld = /*#__PURE__*/ _jsx("div", {
id: "helloWorld"
});
107 changes: 107 additions & 0 deletions tests/jsx_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the snapshot tests instead so that you don't have to setup output fixtures nor repeat the logic that calls transpile. It will also cover bundle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bundle doesn't seem to work with react-jsx and react-jsxdev.
It throws an error (Error: Unable to output during bundling).

This can be avoided by explicitly declaring the import source with /** @jsxImportSource https://esm.sh/react@17.0.2 */ in the input file.

Apparently, this issue has already been fixed for deno bundle (denoland/deno#13389).
Does this have to be implemented again for deno_emit?

assertEquals,
assertExists,
assertRejects,
} from "https://deno.land/std@0.182.0/testing/asserts.ts";
import { toFileUrl } from "https://deno.land/std@0.182.0/path/mod.ts";
import { transpile } from "../js/mod.ts";
import { resolveFixture } from "./utils.ts";

type ExpectedOutputFile =
| "out_no_transforms.js"
| "out_react.js"
| "out_react_jsx.js"
| "out_react_jsx_custom_import_source.js";

interface CompilerOptions {
jsx?: "preserve" | "react-jsx" | "react-jsxdev" | "react-native" | "react";
jsxImportSource?: string;
jsxFactory?: string;
jsxFragmentFactory?: string;
}

interface TestCase {
compilerOptions: CompilerOptions | undefined;
expectedError?: boolean;
expectedOutput?: ExpectedOutputFile;
}

const cases: TestCase[] = [
{
compilerOptions: {},
expectedOutput: "out_react.js",
},
{
compilerOptions: { jsx: "react" },
expectedOutput: "out_react.js",
},
{
compilerOptions: { jsx: "react-native" },
expectedOutput: "out_no_transforms.js",
},
{
compilerOptions: { jsx: "preserve" },
expectedOutput: "out_no_transforms.js",
},

{
compilerOptions: { jsx: "react-jsx" },
expectedOutput: "out_react_jsx.js",
},

{
compilerOptions: { jsx: "react-jsx", jsxImportSource: "example" },
expectedOutput: "out_react_jsx_custom_import_source.js",
},
];

async function testJSXTransform(
t: Deno.TestContext,
testCase: TestCase,
fn: (options?: CompilerOptions) => Promise<string>,
): Promise<void> {
await t.step({
name: `${
testCase.expectedError ? "errors" : `emits ${testCase.expectedOutput}`
} when compilerOptions is set to ${
JSON.stringify(testCase.compilerOptions)
}`,
async fn() {
const run = fn.bind(null, testCase.compilerOptions);

if (testCase.expectedError) {
await assertRejects(run, "bundle throws an error");
} else {
const generatedContent = await run();

const filePath = resolveFixture(`jsx/${testCase.expectedOutput}`);
const expectedContent = await Deno.readTextFile(filePath);

assertEquals(
generatedContent,
expectedContent,
"unexpected generated jsx code",
);
}
},
});
}

Deno.test({
name: "jsx compiler options are respected",
async fn(t) {
await t.step("transpile", async (t) => {
for (const testCase of cases) {
await testJSXTransform(t, testCase, async (compilerOptions) => {
const filePath = resolveFixture("jsx/in.tsx");
const fileUrl = toFileUrl(filePath).toString();

const result = await transpile(filePath, { compilerOptions });
const code = result.get(fileUrl);
assertExists(code);
return code;
});
}
});
},
});
10 changes: 6 additions & 4 deletions wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub struct CompilerOptions {
pub jsx: String,
pub jsx_factory: String,
pub jsx_fragment_factory: String,
pub jsx_import_source: Option<String>,
pub source_map: bool,
}

Expand All @@ -43,6 +44,7 @@ impl Default for CompilerOptions {
jsx: "react".to_string(),
jsx_factory: "React.createElement".to_string(),
jsx_fragment_factory: "React.Fragment".to_string(),
jsx_import_source: None,
source_map: false,
}
}
Expand All @@ -64,12 +66,12 @@ impl From<CompilerOptions> for EmitOptions {
inline_sources: options.inline_sources,
jsx_factory: options.jsx_factory,
jsx_fragment_factory: options.jsx_fragment_factory,
transform_jsx: options.jsx == "react",
transform_jsx: options.jsx == "react" || options.jsx == "react-jsx" || options.jsx == "react-jsxdev",
var_decl_imports: false,
source_map: options.source_map,
jsx_automatic: false,
jsx_development: false,
jsx_import_source: None,
jsx_automatic: options.jsx == "react-jsx" || options.jsx == "react-jsxdev",
jsx_development: options.jsx == "react-jsxdev",
jsx_import_source: options.jsx_import_source,
}
}
}
Expand Down