Skip to content

Commit

Permalink
try to implement jsxRuntime option
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber committed Dec 24, 2024
1 parent 177ffdd commit 35c748b
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 12 deletions.
9 changes: 8 additions & 1 deletion packages/react-live/src/components/Live/LiveProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useEffect, useState, ComponentType, PropsWithChildren } from "react";
import LiveContext from "./LiveContext";
import { generateElement, renderElementAsync } from "../../utils/transpile";
import {
generateElement,
renderElementAsync,
type GenerateOptions,
} from "../../utils/transpile";
import { themes } from "prism-react-renderer";

type ProviderState = {
Expand All @@ -13,6 +17,7 @@ type Props = {
code?: string;
disabled?: boolean;
enableTypeScript?: boolean;
jsxRuntime?: GenerateOptions["jsxRuntime"];
language?: string;
noInline?: boolean;
scope?: Record<string, unknown>;
Expand All @@ -26,6 +31,7 @@ function LiveProvider({
language = "tsx",
theme,
enableTypeScript = true,
jsxRuntime,
disabled = false,
scope,
transformCode,
Expand Down Expand Up @@ -67,6 +73,7 @@ function LiveProvider({
code: transformedCode,
scope,
enableTypeScript,
jsxRuntime,
};

if (noInline) {
Expand Down
8 changes: 8 additions & 0 deletions packages/react-live/src/utils/test/transpile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ describe("transpile", () => {
expect(wrapper.html()).toBe(code);
});

it("should transpile JSX - automatic runtime", () => {
const code = "<div>Hello World!</div>";
const Component = generateElement({ code, jsxRuntime: "automatic" });
const wrapper = shallow(<Component />);

expect(wrapper.html()).toBe(code);
});

it("should transpile PFCs", () => {
const code = "() => <div>Hello World!</div>";
const Component = generateElement({ code });
Expand Down
48 changes: 40 additions & 8 deletions packages/react-live/src/utils/transpile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,40 @@ import transform from "./transform";
import errorBoundary from "./errorBoundary";
import evalCode from "./evalCode";
import compose from "./compose";
import { Transform } from "sucrase";
import { Transform, Options as SucraseOptions } from "sucrase";

const jsxConst = 'const _jsxFileName = "";';
const trimCode = (code: string) => code.trim().replace(/;$/, "");
const spliceJsxConst = (code: string) => code.replace(jsxConst, "").trim();
const addJsxConst = (code: string) => jsxConst + code;
const wrapReturn = (code: string) => `return (${code})`;

type GenerateOptions = {
export type GenerateOptions = {
code: string;
scope?: Record<string, unknown>;
enableTypeScript: boolean;
jsxRuntime?: SucraseOptions["jsxRuntime"];
};

function createRequire() {
return (val: string) => {
if (val === "react/jsx-runtime") {
return require("react/jsx-runtime");
} else if (val === "react/jsx-dev-runtime") {
return require("react/jsx-dev-runtime");
} else {
return require(val);
}
};
}

export const generateElement = (
{ code = "", scope = {}, enableTypeScript = true }: GenerateOptions,
{
code = "",
scope = {},
enableTypeScript = true,
jsxRuntime,
}: GenerateOptions,
errorCallback: (error: Error) => void
) => {
/**
Expand All @@ -34,22 +52,31 @@ export const generateElement = (

const transformed = compose<string>(
addJsxConst,
transform({ transforms: ["imports"] }),
transform({ transforms: ["imports"], jsxRuntime }),
spliceJsxConst,
trimCode,
transform({ transforms: firstPassTransforms }),
transform({ transforms: firstPassTransforms, jsxRuntime }),
wrapReturn,
trimCode
)(code);

return errorBoundary(
evalCode(transformed, { React, ...scope }),
evalCode(transformed, {
React,
require: createRequire(),
...scope,
}),
errorCallback
);
};

export const renderElementAsync = (
{ code = "", scope = {}, enableTypeScript = true }: GenerateOptions,
{
code = "",
scope = {},
enableTypeScript = true,
jsxRuntime,
}: GenerateOptions,
resultCallback: (sender: ComponentType) => void,
errorCallback: (error: Error) => void
// eslint-disable-next-line consistent-return
Expand All @@ -71,5 +98,10 @@ export const renderElementAsync = (
const transforms: Transform[] = ["jsx", "imports"];
enableTypeScript && transforms.splice(1, 0, "typescript");

evalCode(transform({ transforms })(code), { React, ...scope, render });
evalCode(transform({ transforms, jsxRuntime })(code), {
React,
require: createRequire(),
...scope,
render,
});
};
17 changes: 14 additions & 3 deletions packages/react-live/src/utils/transpile/transform.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { transform as _transform, Transform } from "sucrase";
import {
transform as _transform,
Transform,
Options as SucraseOptions,
} from "sucrase";

const defaultTransforms: Transform[] = ["jsx", "imports"];

type Options = {
transforms?: Transform[];
jsxRuntime?: SucraseOptions["jsxRuntime"];
};

export default function transform(opts: Options = {}) {
function toSucraseOptions(opts: Options = {}): SucraseOptions {
const transforms = Array.isArray(opts.transforms)
? opts.transforms.filter(Boolean)
: defaultTransforms;

return (code: string) => _transform(code, { transforms }).code;
return { ...opts, transforms };
}

export default function transform(opts: Options = {}) {
const sucraseOptions = toSucraseOptions(opts);

return (code: string) => _transform(code, sucraseOptions).code;
}

0 comments on commit 35c748b

Please sign in to comment.