From 35c748ba0aa05181b71976df5fe21bee7065b55e Mon Sep 17 00:00:00 2001 From: sebastien Date: Tue, 24 Dec 2024 13:19:34 +0100 Subject: [PATCH] try to implement jsxRuntime option --- .../src/components/Live/LiveProvider.tsx | 9 +++- .../src/utils/test/transpile.test.js | 8 ++++ .../react-live/src/utils/transpile/index.ts | 48 +++++++++++++++---- .../src/utils/transpile/transform.ts | 17 +++++-- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/packages/react-live/src/components/Live/LiveProvider.tsx b/packages/react-live/src/components/Live/LiveProvider.tsx index 030dcbb5..a1ecc789 100644 --- a/packages/react-live/src/components/Live/LiveProvider.tsx +++ b/packages/react-live/src/components/Live/LiveProvider.tsx @@ -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 = { @@ -13,6 +17,7 @@ type Props = { code?: string; disabled?: boolean; enableTypeScript?: boolean; + jsxRuntime?: GenerateOptions["jsxRuntime"]; language?: string; noInline?: boolean; scope?: Record; @@ -26,6 +31,7 @@ function LiveProvider({ language = "tsx", theme, enableTypeScript = true, + jsxRuntime, disabled = false, scope, transformCode, @@ -67,6 +73,7 @@ function LiveProvider({ code: transformedCode, scope, enableTypeScript, + jsxRuntime, }; if (noInline) { diff --git a/packages/react-live/src/utils/test/transpile.test.js b/packages/react-live/src/utils/test/transpile.test.js index 6f045d09..8d095957 100644 --- a/packages/react-live/src/utils/test/transpile.test.js +++ b/packages/react-live/src/utils/test/transpile.test.js @@ -12,6 +12,14 @@ describe("transpile", () => { expect(wrapper.html()).toBe(code); }); + it("should transpile JSX - automatic runtime", () => { + const code = "
Hello World!
"; + const Component = generateElement({ code, jsxRuntime: "automatic" }); + const wrapper = shallow(); + + expect(wrapper.html()).toBe(code); + }); + it("should transpile PFCs", () => { const code = "() =>
Hello World!
"; const Component = generateElement({ code }); diff --git a/packages/react-live/src/utils/transpile/index.ts b/packages/react-live/src/utils/transpile/index.ts index a939057d..c7a45cbe 100644 --- a/packages/react-live/src/utils/transpile/index.ts +++ b/packages/react-live/src/utils/transpile/index.ts @@ -3,7 +3,7 @@ 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(/;$/, ""); @@ -11,14 +11,32 @@ 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; 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 ) => { /** @@ -34,22 +52,31 @@ export const generateElement = ( const transformed = compose( 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 @@ -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, + }); }; diff --git a/packages/react-live/src/utils/transpile/transform.ts b/packages/react-live/src/utils/transpile/transform.ts index f00b04aa..2acbbe5c 100644 --- a/packages/react-live/src/utils/transpile/transform.ts +++ b/packages/react-live/src/utils/transpile/transform.ts @@ -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; }