From 2ba144f171ba4853636b306c48ef1a50dd97d414 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Thu, 18 Jan 2024 23:13:32 +0200 Subject: [PATCH 01/30] feat(compilers): render React component if it is the default export --- src/livecodes/languages/jsx/jsx-runtime.ts | 11 +++++++++ .../languages/typescript/lang-typescript.ts | 14 +++++++---- src/livecodes/result/result-page.ts | 24 ++++++++++++++++--- 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 src/livecodes/languages/jsx/jsx-runtime.ts diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts new file mode 100644 index 000000000..fdcf8130b --- /dev/null +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -0,0 +1,11 @@ +export const reactRuntime = ` +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from './script'; +const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement('div'))); +root.render(React.createElement(App, null)); +`; + +export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); + +export const hasDefaultExport = (code: string) => new RegExp(/export\s*default\s/).test(code); diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index e6c826dcd..7a6464900 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -2,6 +2,8 @@ import type { LanguageSpecs } from '../../models'; import { typescriptUrl } from '../../vendors'; import { getLanguageCustomSettings } from '../../utils'; import { parserPlugins } from '../prettier'; +// eslint-disable-next-line import/no-internal-modules +import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; export const typescriptOptions = { target: 'es2015', @@ -22,12 +24,16 @@ export const typescript: LanguageSpecs = { url: typescriptUrl, factory: () => - async (code, { config, language }) => - (window as any).ts.transpile(code, { + async (code, { config }) => { + if (['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code)) { + code = `import React from 'react';\n${code}`; + } + return (window as any).ts.transpile(code, { ...typescriptOptions, ...getLanguageCustomSettings('typescript', config), - ...getLanguageCustomSettings(language, config), - }), + ...getLanguageCustomSettings(config.script.language, config), + }); + }, }, extensions: ['ts', 'typescript'], editor: 'script', diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 5ca39a638..c13d70e76 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-internal-modules */ import { createImportMap, createCSSModulesImportMap, @@ -7,9 +8,9 @@ import { removeImports, } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; +import { hasCustomJsxRuntime, hasDefaultExport, reactRuntime } from '../languages/jsx/jsx-runtime'; import type { Cache, EditorId, Config, CompileInfo } from '../models'; import { getAppCDN, modulesService } from '../services'; -// eslint-disable-next-line import/no-internal-modules import { testImports } from '../toolspane/test-imports'; import { addAttrs, @@ -156,6 +157,12 @@ export const createResultPage = async ({ getImports(markup).includes('./script') || (runTests && !forExport && getImports(compiledTests).includes('./script')); + const shouldInsertReactJsxRuntime = + ['jsx', 'tsx'].includes(code.script.language) && + hasDefaultExport(code.script.compiled) && + !hasCustomJsxRuntime(code.script.content || '') && + !importFromScript; + let compilerImports = {}; for (const { language, compiled } of runtimeDependencies) { @@ -218,10 +225,13 @@ export const createResultPage = async ({ ...(hasImports(code.markup.compiled) ? createImportMap(code.markup.compiled, config) : {}), + ...(shouldInsertReactJsxRuntime ? createImportMap(reactRuntime, config) : {}), ...(runTests && !forExport && hasImports(compiledTests) ? createImportMap(compiledTests, config) : {}), - ...(importFromScript ? { './script': toDataUrl(code.script.compiled) } : {}), + ...(importFromScript || shouldInsertReactJsxRuntime + ? { './script': toDataUrl(code.script.compiled) } + : {}), ...createCSSModulesImportMap( code.script.compiled, code.style.compiled, @@ -263,7 +273,7 @@ export const createResultPage = async ({ dom.head.appendChild(externalScript); }); - if (!importFromScript) { + if (!importFromScript && !shouldInsertReactJsxRuntime) { // editor script const script = code.script.compiled; const scriptElement = dom.createElement('script'); @@ -288,6 +298,14 @@ export const createResultPage = async ({ } } + // React JSX runtime + if (shouldInsertReactJsxRuntime) { + const jsxRuntimeScript = dom.createElement('script'); + jsxRuntimeScript.type = 'module'; + jsxRuntimeScript.innerHTML = reactRuntime; + dom.body.appendChild(jsxRuntimeScript); + } + // spacing if (config.showSpacing && !forExport) { const spacingScript = dom.createElement('script'); From 87936277aae60e3d5d3d6ebb2f82d966eca5c709 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 19 Jan 2024 07:49:24 +0200 Subject: [PATCH 02/30] feat(compilers): set typescript option { jsx: 'react-jsx' } --- src/livecodes/languages/jsx/jsx-runtime.ts | 4 ++-- .../languages/typescript/lang-typescript.ts | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts index fdcf8130b..33cee2a7d 100644 --- a/src/livecodes/languages/jsx/jsx-runtime.ts +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -1,9 +1,9 @@ export const reactRuntime = ` -import React from "react"; +import { jsx as _jsx } from "react/jsx-runtime"; import { createRoot } from "react-dom/client"; import App from './script'; const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement('div'))); -root.render(React.createElement(App, null)); +root.render(_jsx(App, {})); `; export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index 7a6464900..bb8c578c6 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -7,7 +7,7 @@ import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; export const typescriptOptions = { target: 'es2015', - jsx: 'react', + jsx: 'react-jsx', allowUmdGlobalAccess: true, esModuleInterop: true, }; @@ -24,16 +24,13 @@ export const typescript: LanguageSpecs = { url: typescriptUrl, factory: () => - async (code, { config }) => { - if (['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code)) { - code = `import React from 'react';\n${code}`; - } - return (window as any).ts.transpile(code, { + async (code, { config }) => + (window as any).ts.transpile(code, { ...typescriptOptions, + ...(hasCustomJsxRuntime(code) ? { jsx: 'react' } : {}), ...getLanguageCustomSettings('typescript', config), ...getLanguageCustomSettings(config.script.language, config), - }); - }, + }), }, extensions: ['ts', 'typescript'], editor: 'script', From 4a287aeddc20fe70ce2ad1eefdc38a860c0dd57b Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 19 Jan 2024 08:31:02 +0200 Subject: [PATCH 03/30] only apply {jsx: 'react-jsx'} if language is JSX or TSX this avoids interfering with other languages (e.g. Vue with JSX) --- src/livecodes/languages/typescript/lang-typescript.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index bb8c578c6..16ac76dda 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -7,7 +7,7 @@ import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; export const typescriptOptions = { target: 'es2015', - jsx: 'react-jsx', + jsx: 'react', allowUmdGlobalAccess: true, esModuleInterop: true, }; @@ -27,7 +27,9 @@ export const typescript: LanguageSpecs = { async (code, { config }) => (window as any).ts.transpile(code, { ...typescriptOptions, - ...(hasCustomJsxRuntime(code) ? { jsx: 'react' } : {}), + ...(['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code) + ? { jsx: 'react-jsx' } + : {}), ...getLanguageCustomSettings('typescript', config), ...getLanguageCustomSettings(config.script.language, config), }), From 6daace7f4196e05b9704adfedadedae39cd69187 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Sun, 21 Jan 2024 17:48:32 +0200 Subject: [PATCH 04/30] feat(compilers): render React component if it is the default export --- src/livecodes/languages/jsx/jsx-runtime.ts | 11 +++++++++ .../languages/typescript/lang-typescript.ts | 14 +++++++---- src/livecodes/result/result-page.ts | 24 ++++++++++++++++--- 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 src/livecodes/languages/jsx/jsx-runtime.ts diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts new file mode 100644 index 000000000..fdcf8130b --- /dev/null +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -0,0 +1,11 @@ +export const reactRuntime = ` +import React from "react"; +import { createRoot } from "react-dom/client"; +import App from './script'; +const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement('div'))); +root.render(React.createElement(App, null)); +`; + +export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); + +export const hasDefaultExport = (code: string) => new RegExp(/export\s*default\s/).test(code); diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index e6c826dcd..7a6464900 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -2,6 +2,8 @@ import type { LanguageSpecs } from '../../models'; import { typescriptUrl } from '../../vendors'; import { getLanguageCustomSettings } from '../../utils'; import { parserPlugins } from '../prettier'; +// eslint-disable-next-line import/no-internal-modules +import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; export const typescriptOptions = { target: 'es2015', @@ -22,12 +24,16 @@ export const typescript: LanguageSpecs = { url: typescriptUrl, factory: () => - async (code, { config, language }) => - (window as any).ts.transpile(code, { + async (code, { config }) => { + if (['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code)) { + code = `import React from 'react';\n${code}`; + } + return (window as any).ts.transpile(code, { ...typescriptOptions, ...getLanguageCustomSettings('typescript', config), - ...getLanguageCustomSettings(language, config), - }), + ...getLanguageCustomSettings(config.script.language, config), + }); + }, }, extensions: ['ts', 'typescript'], editor: 'script', diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 5ca39a638..c13d70e76 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-internal-modules */ import { createImportMap, createCSSModulesImportMap, @@ -7,9 +8,9 @@ import { removeImports, } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; +import { hasCustomJsxRuntime, hasDefaultExport, reactRuntime } from '../languages/jsx/jsx-runtime'; import type { Cache, EditorId, Config, CompileInfo } from '../models'; import { getAppCDN, modulesService } from '../services'; -// eslint-disable-next-line import/no-internal-modules import { testImports } from '../toolspane/test-imports'; import { addAttrs, @@ -156,6 +157,12 @@ export const createResultPage = async ({ getImports(markup).includes('./script') || (runTests && !forExport && getImports(compiledTests).includes('./script')); + const shouldInsertReactJsxRuntime = + ['jsx', 'tsx'].includes(code.script.language) && + hasDefaultExport(code.script.compiled) && + !hasCustomJsxRuntime(code.script.content || '') && + !importFromScript; + let compilerImports = {}; for (const { language, compiled } of runtimeDependencies) { @@ -218,10 +225,13 @@ export const createResultPage = async ({ ...(hasImports(code.markup.compiled) ? createImportMap(code.markup.compiled, config) : {}), + ...(shouldInsertReactJsxRuntime ? createImportMap(reactRuntime, config) : {}), ...(runTests && !forExport && hasImports(compiledTests) ? createImportMap(compiledTests, config) : {}), - ...(importFromScript ? { './script': toDataUrl(code.script.compiled) } : {}), + ...(importFromScript || shouldInsertReactJsxRuntime + ? { './script': toDataUrl(code.script.compiled) } + : {}), ...createCSSModulesImportMap( code.script.compiled, code.style.compiled, @@ -263,7 +273,7 @@ export const createResultPage = async ({ dom.head.appendChild(externalScript); }); - if (!importFromScript) { + if (!importFromScript && !shouldInsertReactJsxRuntime) { // editor script const script = code.script.compiled; const scriptElement = dom.createElement('script'); @@ -288,6 +298,14 @@ export const createResultPage = async ({ } } + // React JSX runtime + if (shouldInsertReactJsxRuntime) { + const jsxRuntimeScript = dom.createElement('script'); + jsxRuntimeScript.type = 'module'; + jsxRuntimeScript.innerHTML = reactRuntime; + dom.body.appendChild(jsxRuntimeScript); + } + // spacing if (config.showSpacing && !forExport) { const spacingScript = dom.createElement('script'); From e46450dd39919415e48896d27dd27ab66a69a417 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Sun, 21 Jan 2024 17:48:32 +0200 Subject: [PATCH 05/30] feat(compilers): set typescript option { jsx: 'react-jsx' } --- src/livecodes/languages/jsx/jsx-runtime.ts | 4 ++-- .../languages/typescript/lang-typescript.ts | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts index fdcf8130b..33cee2a7d 100644 --- a/src/livecodes/languages/jsx/jsx-runtime.ts +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -1,9 +1,9 @@ export const reactRuntime = ` -import React from "react"; +import { jsx as _jsx } from "react/jsx-runtime"; import { createRoot } from "react-dom/client"; import App from './script'; const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement('div'))); -root.render(React.createElement(App, null)); +root.render(_jsx(App, {})); `; export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index 7a6464900..bb8c578c6 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -7,7 +7,7 @@ import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; export const typescriptOptions = { target: 'es2015', - jsx: 'react', + jsx: 'react-jsx', allowUmdGlobalAccess: true, esModuleInterop: true, }; @@ -24,16 +24,13 @@ export const typescript: LanguageSpecs = { url: typescriptUrl, factory: () => - async (code, { config }) => { - if (['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code)) { - code = `import React from 'react';\n${code}`; - } - return (window as any).ts.transpile(code, { + async (code, { config }) => + (window as any).ts.transpile(code, { ...typescriptOptions, + ...(hasCustomJsxRuntime(code) ? { jsx: 'react' } : {}), ...getLanguageCustomSettings('typescript', config), ...getLanguageCustomSettings(config.script.language, config), - }); - }, + }), }, extensions: ['ts', 'typescript'], editor: 'script', From d83c668f600c140cf04843beaea7b9727af78a72 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Sun, 21 Jan 2024 17:48:32 +0200 Subject: [PATCH 06/30] only apply {jsx: 'react-jsx'} if language is JSX or TSX this avoids interfering with other languages (e.g. Vue with JSX) --- src/livecodes/languages/typescript/lang-typescript.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index bb8c578c6..16ac76dda 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -7,7 +7,7 @@ import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; export const typescriptOptions = { target: 'es2015', - jsx: 'react-jsx', + jsx: 'react', allowUmdGlobalAccess: true, esModuleInterop: true, }; @@ -27,7 +27,9 @@ export const typescript: LanguageSpecs = { async (code, { config }) => (window as any).ts.transpile(code, { ...typescriptOptions, - ...(hasCustomJsxRuntime(code) ? { jsx: 'react' } : {}), + ...(['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code) + ? { jsx: 'react-jsx' } + : {}), ...getLanguageCustomSettings('typescript', config), ...getLanguageCustomSettings(config.script.language, config), }), From 2e5b9d1d2e8af0c150426d57b249c8112477d5dc Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Sun, 21 Jan 2024 20:22:09 +0200 Subject: [PATCH 07/30] feat(Templates): update react and jest-react starter templates to use the new jsx runtime --- .../templates/starter/jest-react-starter.ts | 14 ++++++-------- src/livecodes/templates/starter/react-starter.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/livecodes/templates/starter/jest-react-starter.ts b/src/livecodes/templates/starter/jest-react-starter.ts index 9eb5e9619..c82c70b23 100644 --- a/src/livecodes/templates/starter/jest-react-starter.ts +++ b/src/livecodes/templates/starter/jest-react-starter.ts @@ -8,9 +8,7 @@ export const jestReactStarter: Template = { autotest: true, markup: { language: 'html', - content: ` -<div id="app">Loading...</div> -`.trimStart(), + content: '', }, style: { language: 'css', @@ -33,12 +31,11 @@ export const jestReactStarter: Template = { script: { language: 'jsx', content: ` -import React, { useState } from "react"; -import { createRoot } from "react-dom/client"; +import { useState } from "react"; export const increment = (count) => (count ?? 0) + 1; -export default function App(props) { +function Counter(props) { const [count, setCount] = useState(0); return ( <div className="container"> @@ -51,8 +48,9 @@ export default function App(props) { ); } -const root = createRoot(document.querySelector("#app")); -root.render(<App name="Jest with React" />); +export default function App() { + return <Counter name="Jest with React" />; +} `.trimStart(), }, tests: { diff --git a/src/livecodes/templates/starter/react-starter.ts b/src/livecodes/templates/starter/react-starter.ts index fd2b0fa2a..db497a1c3 100644 --- a/src/livecodes/templates/starter/react-starter.ts +++ b/src/livecodes/templates/starter/react-starter.ts @@ -7,7 +7,7 @@ export const reactStarter: Template = { activeEditor: 'script', markup: { language: 'html', - content: '<div id="app">Loading...</div>\n', + content: '', }, style: { language: 'css', @@ -25,10 +25,9 @@ export const reactStarter: Template = { script: { language: 'jsx', content: ` -import React, { useState } from "react"; -import { createRoot } from "react-dom/client"; +import { useState } from "react"; -function App(props) { +function Counter(props) { const [count, setCount] = useState(0); return ( <div className="container"> @@ -40,8 +39,9 @@ function App(props) { ); } -const root = createRoot(document.querySelector("#app")); -root.render(<App name="React" />); +export default function App() { + return <Counter name="React" />; +} `.trimStart(), }, stylesheets: [], From 40a96c7148d5a41e029442e408142aa9b3d7772b Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Mon, 22 Jan 2024 01:14:04 +0200 Subject: [PATCH 08/30] only render jsx default export if it is a react component --- src/livecodes/languages/jsx/jsx-runtime.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts index 33cee2a7d..0287dd57c 100644 --- a/src/livecodes/languages/jsx/jsx-runtime.ts +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -1,9 +1,13 @@ export const reactRuntime = ` import { jsx as _jsx } from "react/jsx-runtime"; import { createRoot } from "react-dom/client"; -import App from './script'; -const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement('div'))); -root.render(_jsx(App, {})); +import App from "./script"; +(() => { + const isReactComponent = (c) => typeof c === "function" && (/return\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype.isReactComponent)); + if (!isReactComponent(App)) return; + const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div"))); + root.render(_jsx(App, {})); +})(); `; export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); From 7db03f6e39a05beb56d51c8d1798564b0f52536f Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 01:12:43 +0200 Subject: [PATCH 09/30] feat(compilers): render react-native component if it is the default export --- .../languages/react-native/jsx-runtime.ts | 11 ++++++++ .../react-native/lang-react-native-tsx.ts | 21 ++------------- .../react-native/lang-react-native.ts | 3 ++- src/livecodes/result/result-page.ts | 26 +++++++++++++------ .../templates/starter/react-native-starter.ts | 14 +++------- 5 files changed, 37 insertions(+), 38 deletions(-) create mode 100644 src/livecodes/languages/react-native/jsx-runtime.ts diff --git a/src/livecodes/languages/react-native/jsx-runtime.ts b/src/livecodes/languages/react-native/jsx-runtime.ts new file mode 100644 index 000000000..6f0794030 --- /dev/null +++ b/src/livecodes/languages/react-native/jsx-runtime.ts @@ -0,0 +1,11 @@ +export const reactNativeRuntime = ` +import { AppRegistry } from "react-native"; +import App from "./script"; +(() => { + const isReactComponent = (c) => typeof c === "function" && (/return\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype.isReactComponent)); + if (!isReactComponent(App)) return; + const rootTag = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); + AppRegistry.registerComponent("App", () => App); + AppRegistry.runApplication("App", { rootTag }); +})(); +`; diff --git a/src/livecodes/languages/react-native/lang-react-native-tsx.ts b/src/livecodes/languages/react-native/lang-react-native-tsx.ts index fe008dbef..8e55b94df 100644 --- a/src/livecodes/languages/react-native/lang-react-native-tsx.ts +++ b/src/livecodes/languages/react-native/lang-react-native-tsx.ts @@ -1,32 +1,15 @@ import type { LanguageSpecs } from '../../models'; -import { typescriptOptions } from '../typescript'; -import { getLanguageCustomSettings } from '../utils'; import { parserPlugins } from '../prettier'; -import { reactNativeWebUrl } from './lang-react-native'; export const reactNativeTsx: LanguageSpecs = { name: 'react-native-tsx', title: 'RN (TSX)', longTitle: 'React Native (TSX)', parser: { - name: 'babel', + name: 'babel-ts', pluginUrls: [parserPlugins.babel, parserPlugins.html], }, - compiler: { - dependencies: ['typescript'], - factory: - () => - async (code, { config, language }) => - (window as any).ts.transpile(code, { - ...typescriptOptions, - ...getLanguageCustomSettings('typescript', config), - ...getLanguageCustomSettings(language, config), - }), - imports: { - react: reactNativeWebUrl, - 'react-native': reactNativeWebUrl, - }, - }, + compiler: 'react-native', extensions: ['react-native.tsx'], editor: 'script', editorLanguage: 'typescript', diff --git a/src/livecodes/languages/react-native/lang-react-native.ts b/src/livecodes/languages/react-native/lang-react-native.ts index 3ca25ff2b..c345b8820 100644 --- a/src/livecodes/languages/react-native/lang-react-native.ts +++ b/src/livecodes/languages/react-native/lang-react-native.ts @@ -4,7 +4,7 @@ import { typescriptOptions } from '../typescript'; import { getLanguageCustomSettings } from '../utils'; import { parserPlugins } from '../prettier'; -export const reactNativeWebUrl = vendorsBaseUrl + 'react-native-web/react-native-web.js'; +const reactNativeWebUrl = vendorsBaseUrl + 'react-native-web/react-native-web.js'; export const reactNative: LanguageSpecs = { name: 'react-native', @@ -21,6 +21,7 @@ export const reactNative: LanguageSpecs = { async (code, { config, language }) => (window as any).ts.transpile(code, { ...typescriptOptions, + ...{ jsx: 'react-jsx' }, ...getLanguageCustomSettings('typescript', config), ...getLanguageCustomSettings(language, config), }), diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index c13d70e76..bf559d4cc 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -9,7 +9,8 @@ import { } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; import { hasCustomJsxRuntime, hasDefaultExport, reactRuntime } from '../languages/jsx/jsx-runtime'; -import type { Cache, EditorId, Config, CompileInfo } from '../models'; +import { reactNativeRuntime } from '../languages/react-native/jsx-runtime'; +import type { Cache, EditorId, Config, CompileInfo, Language } from '../models'; import { getAppCDN, modulesService } from '../services'; import { testImports } from '../toolspane/test-imports'; import { @@ -157,8 +158,17 @@ export const createResultPage = async ({ getImports(markup).includes('./script') || (runTests && !forExport && getImports(compiledTests).includes('./script')); - const shouldInsertReactJsxRuntime = - ['jsx', 'tsx'].includes(code.script.language) && + const jsxRuntimes: Partial<Record<Language, string>> = { + jsx: reactRuntime, + tsx: reactRuntime, + 'react-native': reactNativeRuntime, + 'react-native-tsx': reactNativeRuntime, + solid: '', + 'solid.tsx': '', + }; + const jsxRuntime = jsxRuntimes[code.script.language] || ''; + const shouldInsertJsxRuntime = + Object.keys(jsxRuntimes).includes(code.script.language) && hasDefaultExport(code.script.compiled) && !hasCustomJsxRuntime(code.script.content || '') && !importFromScript; @@ -225,11 +235,11 @@ export const createResultPage = async ({ ...(hasImports(code.markup.compiled) ? createImportMap(code.markup.compiled, config) : {}), - ...(shouldInsertReactJsxRuntime ? createImportMap(reactRuntime, config) : {}), + ...(shouldInsertJsxRuntime ? createImportMap(jsxRuntime, config) : {}), ...(runTests && !forExport && hasImports(compiledTests) ? createImportMap(compiledTests, config) : {}), - ...(importFromScript || shouldInsertReactJsxRuntime + ...(importFromScript || shouldInsertJsxRuntime ? { './script': toDataUrl(code.script.compiled) } : {}), ...createCSSModulesImportMap( @@ -273,7 +283,7 @@ export const createResultPage = async ({ dom.head.appendChild(externalScript); }); - if (!importFromScript && !shouldInsertReactJsxRuntime) { + if (!importFromScript && !shouldInsertJsxRuntime) { // editor script const script = code.script.compiled; const scriptElement = dom.createElement('script'); @@ -299,10 +309,10 @@ export const createResultPage = async ({ } // React JSX runtime - if (shouldInsertReactJsxRuntime) { + if (shouldInsertJsxRuntime) { const jsxRuntimeScript = dom.createElement('script'); jsxRuntimeScript.type = 'module'; - jsxRuntimeScript.innerHTML = reactRuntime; + jsxRuntimeScript.innerHTML = jsxRuntime; dom.body.appendChild(jsxRuntimeScript); } diff --git a/src/livecodes/templates/starter/react-native-starter.ts b/src/livecodes/templates/starter/react-native-starter.ts index f22e097d8..7d56f3d78 100644 --- a/src/livecodes/templates/starter/react-native-starter.ts +++ b/src/livecodes/templates/starter/react-native-starter.ts @@ -7,7 +7,7 @@ export const reactNativeStarter: Template = { activeEditor: 'script', markup: { language: 'html', - content: '<div id="app">Loading...</div>\n', + content: '', }, style: { language: 'css', @@ -16,8 +16,8 @@ export const reactNativeStarter: Template = { script: { language: 'react-native', content: ` -import React, { useState } from "react"; -import { AppRegistry, Button, Image, StyleSheet, Text, View } from "react-native"; +import { useState } from "react"; +import { Button, Image, StyleSheet, Text, View } from "react-native"; const logoUri = \`data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z" /><circle cx="420.9" cy="296.5" r="45.7" /><path d="M520.5 78.1z" /></g></svg>\`; @@ -41,7 +41,7 @@ function Counter(props) { ); } -function App() { +export default function App() { return ( <View style={styles.app}> <View style={styles.header}> @@ -92,12 +92,6 @@ const styles = StyleSheet.create({ color: "#1B95E0", }, }); - -AppRegistry.registerComponent("App", () => App); - -AppRegistry.runApplication("App", { - rootTag: document.getElementById("app"), -}); `.trimStart(), }, stylesheets: [], From 4968f036b6062293d6580eab593f4a99cc83ea14 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 02:04:34 +0200 Subject: [PATCH 10/30] feat(compilers): render Solid component if it is the default export --- src/livecodes/languages/solid/jsx-runtime.ts | 10 +++++++ .../languages/solid/lang-solid-tsx.ts | 2 +- src/livecodes/result/result-page.ts | 5 ++-- .../templates/starter/solid-starter.ts | 30 ++++++++++++------- 4 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 src/livecodes/languages/solid/jsx-runtime.ts diff --git a/src/livecodes/languages/solid/jsx-runtime.ts b/src/livecodes/languages/solid/jsx-runtime.ts new file mode 100644 index 000000000..05f5f7fa9 --- /dev/null +++ b/src/livecodes/languages/solid/jsx-runtime.ts @@ -0,0 +1,10 @@ +export const solidRuntime = ` +import { render, createComponent } from "solid-js/web"; +import App from "./script"; +(() => { + const isSolidComponent = (c) => typeof c === "function" && /return\\s+\\(?\\s*function\\s+\\(\\)\\s+{/g.test(String(c)); + if (!isSolidComponent(App)) return; + const root = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); + render(() => createComponent(App, {}), root); +})(); +`; diff --git a/src/livecodes/languages/solid/lang-solid-tsx.ts b/src/livecodes/languages/solid/lang-solid-tsx.ts index d07af1c3d..bde8639ca 100644 --- a/src/livecodes/languages/solid/lang-solid-tsx.ts +++ b/src/livecodes/languages/solid/lang-solid-tsx.ts @@ -5,7 +5,7 @@ export const solidTsx: LanguageSpecs = { name: 'solid.tsx', title: 'Solid (TS)', parser: { - name: 'babel', + name: 'babel-ts', pluginUrls: [parserPlugins.babel, parserPlugins.html], }, compiler: 'solid', diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index bf559d4cc..4f163f5a8 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -10,6 +10,7 @@ import { import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; import { hasCustomJsxRuntime, hasDefaultExport, reactRuntime } from '../languages/jsx/jsx-runtime'; import { reactNativeRuntime } from '../languages/react-native/jsx-runtime'; +import { solidRuntime } from '../languages/solid/jsx-runtime'; import type { Cache, EditorId, Config, CompileInfo, Language } from '../models'; import { getAppCDN, modulesService } from '../services'; import { testImports } from '../toolspane/test-imports'; @@ -163,8 +164,8 @@ export const createResultPage = async ({ tsx: reactRuntime, 'react-native': reactNativeRuntime, 'react-native-tsx': reactNativeRuntime, - solid: '', - 'solid.tsx': '', + solid: solidRuntime, + 'solid.tsx': solidRuntime, }; const jsxRuntime = jsxRuntimes[code.script.language] || ''; const shouldInsertJsxRuntime = diff --git a/src/livecodes/templates/starter/solid-starter.ts b/src/livecodes/templates/starter/solid-starter.ts index 257a3c369..d2f33e09a 100644 --- a/src/livecodes/templates/starter/solid-starter.ts +++ b/src/livecodes/templates/starter/solid-starter.ts @@ -7,7 +7,7 @@ export const solidStarter: Template = { activeEditor: 'script', markup: { language: 'html', - content: '<div id="app"></div>\n', + content: '', }, style: { language: 'css', @@ -25,28 +25,36 @@ export const solidStarter: Template = { script: { language: 'solid.tsx', content: ` -import { render } from "solid-js/web"; import { createSignal } from "solid-js"; -type Props = { - title: string; +function Greeting(props: { name: string }) { + return ( + <> + <h1>Hello, {props.name}!</h1> + <img className="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/solid.svg" /> + </> + ); } -function App(props: Props) { +function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount(count() + 1); - return ( - <div className="container"> - <h1>Hello, {props.title}!</h1> - <img className="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/solid.svg" /> + <> <p>You clicked {count()} times.</p> <button onClick={increment}>Click me</button> - </div> + </> ); } -render(() => <App title="Solid" />, document.getElementById("app")); +export default function App() { + return ( + <div className="container"> + <Greeting name="Solid" /> + <Counter /> + </div> + ); +} `.trimStart(), }, stylesheets: [], From 32270d801683dd556412e8396de6e56f19702431 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 02:56:20 +0200 Subject: [PATCH 11/30] feat(compilers): allow using JSX fragments in Vue SFC --- src/livecodes/languages/vue/lang-vue-compiler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/livecodes/languages/vue/lang-vue-compiler.ts b/src/livecodes/languages/vue/lang-vue-compiler.ts index a3aa970a6..b3dee4bd4 100644 --- a/src/livecodes/languages/vue/lang-vue-compiler.ts +++ b/src/livecodes/languages/vue/lang-vue-compiler.ts @@ -246,7 +246,7 @@ import { getLanguageByAlias } from '../utils'; attrs.toLowerCase().includes("'jsx'") || attrs.toLowerCase().includes("'tsx'") ) { - scriptContent = 'import { h } from "vue";\n' + scriptContent; + scriptContent = 'import { h, Fragment } from "vue";\n' + scriptContent; } return `<script ${attrs}>${scriptContent}</script>`; }); @@ -254,6 +254,7 @@ import { getLanguageByAlias } from '../utils'; config.customSettings.typescript = { ...config.customSettings.typescript, jsxFactory: 'h', + jsxFragmentFactory: 'Fragment', }; content = await compileAllBlocks(content, config, { prepareFn }); From 6f630c2d723c2a9d9d09044cdba368820c7536f4 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 02:56:53 +0200 Subject: [PATCH 12/30] edit starter templates --- .../templates/starter/mdx-starter.ts | 10 ++++----- .../templates/starter/react-starter.ts | 22 ++++++++++++++----- .../templates/starter/vue-sfc-starter.ts | 10 +++++++-- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/livecodes/templates/starter/mdx-starter.ts b/src/livecodes/templates/starter/mdx-starter.ts index 11b446d6f..cad5da47b 100644 --- a/src/livecodes/templates/starter/mdx-starter.ts +++ b/src/livecodes/templates/starter/mdx-starter.ts @@ -8,9 +8,9 @@ export const mdxStarter: Template = { markup: { language: 'mdx', content: ` -import { Hello, Counter } from './script'; +import { Greeting, Counter } from './script'; -<Hello title="MDX" /> +<Greeting name="MDX" /> ![MDX Logo]({{ __livecodes_baseUrl__ }}assets/templates/mdx.svg) @@ -33,11 +33,11 @@ img { script: { language: 'jsx', content: ` -import React, { useState } from "react"; +import { useState } from "react"; -export const Hello = (props) => <h1>Hello, {props.title || "World"}!</h1>; +export const Greeting = (props) => <h1>Hello, {props.name || "World"}!</h1>; -export function Counter(props) { +export function Counter() { const [count, setCount] = useState(0); return ( <div> diff --git a/src/livecodes/templates/starter/react-starter.ts b/src/livecodes/templates/starter/react-starter.ts index db497a1c3..43fc1bca1 100644 --- a/src/livecodes/templates/starter/react-starter.ts +++ b/src/livecodes/templates/starter/react-starter.ts @@ -27,20 +27,32 @@ export const reactStarter: Template = { content: ` import { useState } from "react"; -function Counter(props) { - const [count, setCount] = useState(0); +function Greeting(props) { return ( - <div className="container"> + <> <h1>Hello, {props.name}!</h1> <img className="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/react.svg" /> + </> + ); +} + +function Counter() { + const [count, setCount] = useState(0); + return ( + <> <p>You clicked {count} times.</p> <button onClick={() => setCount(count + 1)}>Click me</button> - </div> + </> ); } export default function App() { - return <Counter name="React" />; + return ( + <div className="container"> + <Greeting name="React" /> + <Counter /> + </div> + ); } `.trimStart(), }, diff --git a/src/livecodes/templates/starter/vue-sfc-starter.ts b/src/livecodes/templates/starter/vue-sfc-starter.ts index 6dd83bdf6..caf750437 100644 --- a/src/livecodes/templates/starter/vue-sfc-starter.ts +++ b/src/livecodes/templates/starter/vue-sfc-starter.ts @@ -16,16 +16,22 @@ export const vueSfcStarter: Template = { script: { language: 'vue', content: ` -<script setup> +<script setup lang="tsx"> import { ref } from 'vue'; + const name = 'Vue'; const count = ref(0); const align = 'center'; + + // define inline component + function Greeting(props: {name: string}) { + return <h1>Hello, { props.name }!</h1> + } </script> <template> <div class="container"> - <h1>Hello, Vue!</h1> + <Greeting :name="name" /> <img class="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/vue.svg" /> <p>You clicked {{ count }} times.</p> <button @click="count++">Click me</button> From c853df67a07e5954f5e8f138f385ef02d0e82e95 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 03:20:17 +0200 Subject: [PATCH 13/30] fix jsx runtime code --- src/livecodes/languages/jsx/jsx-runtime.ts | 2 +- src/livecodes/languages/react-native/jsx-runtime.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts index 0287dd57c..5de841c97 100644 --- a/src/livecodes/languages/jsx/jsx-runtime.ts +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime"; import { createRoot } from "react-dom/client"; import App from "./script"; (() => { - const isReactComponent = (c) => typeof c === "function" && (/return\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype.isReactComponent)); + const isReactComponent = (c) => typeof c === "function" && (/(return|=>)\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype?.isReactComponent)); if (!isReactComponent(App)) return; const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div"))); root.render(_jsx(App, {})); diff --git a/src/livecodes/languages/react-native/jsx-runtime.ts b/src/livecodes/languages/react-native/jsx-runtime.ts index 6f0794030..b02593671 100644 --- a/src/livecodes/languages/react-native/jsx-runtime.ts +++ b/src/livecodes/languages/react-native/jsx-runtime.ts @@ -2,7 +2,7 @@ export const reactNativeRuntime = ` import { AppRegistry } from "react-native"; import App from "./script"; (() => { - const isReactComponent = (c) => typeof c === "function" && (/return\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype.isReactComponent)); + const isReactComponent = (c) => typeof c === "function" && (/(return|=>)\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype?.isReactComponent)); if (!isReactComponent(App)) return; const rootTag = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); AppRegistry.registerComponent("App", () => App); From 89111d00290f2b562ae893d651973aba12c2a508 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 04:20:36 +0200 Subject: [PATCH 14/30] remove `import './styles.css'` from script --- src/livecodes/compiler/import-map.ts | 3 +++ src/livecodes/result/result-page.ts | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/livecodes/compiler/import-map.ts b/src/livecodes/compiler/import-map.ts index ed07b62dc..571c68070 100644 --- a/src/livecodes/compiler/import-map.ts +++ b/src/livecodes/compiler/import-map.ts @@ -199,6 +199,9 @@ export const removeImports = (code: string, mods: string[]) => return mods.includes(libName) ? '' : statement; }); +export const removeSideEffectStyleImports = (code: string) => + code.replace(/import\s+["']\.\/style(s)?\.(css|less|sass|scss)["'];?/g, ''); + export const styleimportsPattern = /(?:@import\s+?)((?:".*?")|(?:'.*?')|(?:url\('.*?'\))|(?:url\(".*?"\)))(.*)?;/g; diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 4f163f5a8..83fc9208c 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -6,6 +6,7 @@ import { hasImports, isModuleScript, removeImports, + removeSideEffectStyleImports, } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; import { hasCustomJsxRuntime, hasDefaultExport, reactRuntime } from '../languages/jsx/jsx-runtime'; @@ -118,6 +119,7 @@ export const createResultPage = async ({ dom.head.appendChild(stylesheet); }); code.script.compiled = removeImports(code.script.compiled, stylesheetImports); + code.script.compiled = removeSideEffectStyleImports(code.script.compiled); // editor styles if (singleFile) { From d6a1803a7825d31af6c2ed9b0727cd8537c9a691 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 05:20:37 +0200 Subject: [PATCH 15/30] fix --- src/livecodes/compiler/import-map.ts | 4 +++- src/livecodes/languages/jsx/jsx-runtime.ts | 2 -- src/livecodes/result/result-page.ts | 11 ++++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/livecodes/compiler/import-map.ts b/src/livecodes/compiler/import-map.ts index 571c68070..0ae45311f 100644 --- a/src/livecodes/compiler/import-map.ts +++ b/src/livecodes/compiler/import-map.ts @@ -90,6 +90,8 @@ export const hasImports = (code: string) => getImports(code).length > 0; export const hasExports = (code: string) => new RegExp(/(^export\s)|([\s|;]export\s)/).test(removeCommentsAndStrings(code)); +export const hasDefaultExport = (code: string) => new RegExp(/export\s*default\s/).test(code); + export const hasUrlImportsOrExports = (code: string) => new RegExp( /((?:import|export)\s+?(?:(?:(?:[\w*\s{},\$]*)\s+from\s+?)|))((?:"(?:\.|http|\/).*?")|(?:'(?:\.|http|\/).*?'))([\s]*?(?:;|$|))/, @@ -199,7 +201,7 @@ export const removeImports = (code: string, mods: string[]) => return mods.includes(libName) ? '' : statement; }); -export const removeSideEffectStyleImports = (code: string) => +export const removeEditorStylesImport = (code: string) => code.replace(/import\s+["']\.\/style(s)?\.(css|less|sass|scss)["'];?/g, ''); export const styleimportsPattern = diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/jsx-runtime.ts index 5de841c97..a8f103ebf 100644 --- a/src/livecodes/languages/jsx/jsx-runtime.ts +++ b/src/livecodes/languages/jsx/jsx-runtime.ts @@ -11,5 +11,3 @@ import App from "./script"; `; export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); - -export const hasDefaultExport = (code: string) => new RegExp(/export\s*default\s/).test(code); diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 83fc9208c..e56f32fa4 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -6,10 +6,12 @@ import { hasImports, isModuleScript, removeImports, - removeSideEffectStyleImports, + // avoid default exports conflict + removeEditorStylesImport, + hasDefaultExport, } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; -import { hasCustomJsxRuntime, hasDefaultExport, reactRuntime } from '../languages/jsx/jsx-runtime'; +import { hasCustomJsxRuntime, reactRuntime } from '../languages/jsx/jsx-runtime'; import { reactNativeRuntime } from '../languages/react-native/jsx-runtime'; import { solidRuntime } from '../languages/solid/jsx-runtime'; import type { Cache, EditorId, Config, CompileInfo, Language } from '../models'; @@ -119,7 +121,10 @@ export const createResultPage = async ({ dom.head.appendChild(stylesheet); }); code.script.compiled = removeImports(code.script.compiled, stylesheetImports); - code.script.compiled = removeSideEffectStyleImports(code.script.compiled); + if (hasDefaultExport(code.script.compiled)) { + // avoid default exports conflict + code.script.compiled = removeEditorStylesImport(code.script.compiled); + } // editor styles if (singleFile) { From dd5167d4945679cf04c48932ca9356ec0d236fd5 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Tue, 23 Jan 2024 13:46:46 +0200 Subject: [PATCH 16/30] improve detecting custom JSX runtime --- .../jsx/{jsx-runtime.ts => react-runtime.ts} | 2 -- ...{jsx-runtime.ts => react-native-runtime.ts} | 0 .../solid/{jsx-runtime.ts => solid-runtime.ts} | 0 .../languages/typescript/lang-typescript.ts | 18 ++++++++++++++---- src/livecodes/result/result-page.ts | 9 +++++---- 5 files changed, 19 insertions(+), 10 deletions(-) rename src/livecodes/languages/jsx/{jsx-runtime.ts => react-runtime.ts} (84%) rename src/livecodes/languages/react-native/{jsx-runtime.ts => react-native-runtime.ts} (100%) rename src/livecodes/languages/solid/{jsx-runtime.ts => solid-runtime.ts} (100%) diff --git a/src/livecodes/languages/jsx/jsx-runtime.ts b/src/livecodes/languages/jsx/react-runtime.ts similarity index 84% rename from src/livecodes/languages/jsx/jsx-runtime.ts rename to src/livecodes/languages/jsx/react-runtime.ts index a8f103ebf..75eda3f5e 100644 --- a/src/livecodes/languages/jsx/jsx-runtime.ts +++ b/src/livecodes/languages/jsx/react-runtime.ts @@ -9,5 +9,3 @@ import App from "./script"; root.render(_jsx(App, {})); })(); `; - -export const hasCustomJsxRuntime = (code: string) => new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code); diff --git a/src/livecodes/languages/react-native/jsx-runtime.ts b/src/livecodes/languages/react-native/react-native-runtime.ts similarity index 100% rename from src/livecodes/languages/react-native/jsx-runtime.ts rename to src/livecodes/languages/react-native/react-native-runtime.ts diff --git a/src/livecodes/languages/solid/jsx-runtime.ts b/src/livecodes/languages/solid/solid-runtime.ts similarity index 100% rename from src/livecodes/languages/solid/jsx-runtime.ts rename to src/livecodes/languages/solid/solid-runtime.ts diff --git a/src/livecodes/languages/typescript/lang-typescript.ts b/src/livecodes/languages/typescript/lang-typescript.ts index 16ac76dda..b00e3f30c 100644 --- a/src/livecodes/languages/typescript/lang-typescript.ts +++ b/src/livecodes/languages/typescript/lang-typescript.ts @@ -1,9 +1,19 @@ -import type { LanguageSpecs } from '../../models'; +import type { Config, LanguageSpecs } from '../../models'; import { typescriptUrl } from '../../vendors'; import { getLanguageCustomSettings } from '../../utils'; import { parserPlugins } from '../prettier'; -// eslint-disable-next-line import/no-internal-modules -import { hasCustomJsxRuntime } from '../jsx/jsx-runtime'; + +export const hasCustomJsxRuntime = (code: string, config: Config) => { + const customTSConfig = { + ...getLanguageCustomSettings('typescript', config), + ...getLanguageCustomSettings(config.script.language, config), + }; + return Boolean( + customTSConfig.jsx || + customTSConfig.jsxFactory || + new RegExp(/\/\*\*[\s\*]*@jsx\s/g).test(code), + ); +}; export const typescriptOptions = { target: 'es2015', @@ -27,7 +37,7 @@ export const typescript: LanguageSpecs = { async (code, { config }) => (window as any).ts.transpile(code, { ...typescriptOptions, - ...(['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code) + ...(['jsx', 'tsx'].includes(config.script.language) && !hasCustomJsxRuntime(code, config) ? { jsx: 'react-jsx' } : {}), ...getLanguageCustomSettings('typescript', config), diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index e56f32fa4..552816572 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -11,9 +11,10 @@ import { hasDefaultExport, } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; -import { hasCustomJsxRuntime, reactRuntime } from '../languages/jsx/jsx-runtime'; -import { reactNativeRuntime } from '../languages/react-native/jsx-runtime'; -import { solidRuntime } from '../languages/solid/jsx-runtime'; +import { hasCustomJsxRuntime } from '../languages/typescript'; +import { reactRuntime } from '../languages/jsx/react-runtime'; +import { reactNativeRuntime } from '../languages/react-native/react-native-runtime'; +import { solidRuntime } from '../languages/solid/solid-runtime'; import type { Cache, EditorId, Config, CompileInfo, Language } from '../models'; import { getAppCDN, modulesService } from '../services'; import { testImports } from '../toolspane/test-imports'; @@ -178,7 +179,7 @@ export const createResultPage = async ({ const shouldInsertJsxRuntime = Object.keys(jsxRuntimes).includes(code.script.language) && hasDefaultExport(code.script.compiled) && - !hasCustomJsxRuntime(code.script.content || '') && + !hasCustomJsxRuntime(code.script.content || '', config) && !importFromScript; let compilerImports = {}; From 3397a659c12349ea772d745db93501b62a257970 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Wed, 24 Jan 2024 00:05:35 +0200 Subject: [PATCH 17/30] test(compilers): add e2e tests for jsx --- docs/docs/languages/jsx.md | 54 +++++++++- e2e/specs/jsx.spec.ts | 156 +++++++++++++++++++++++++++ src/livecodes/compiler/import-map.ts | 3 - src/livecodes/result/result-page.ts | 24 +++-- 4 files changed, 225 insertions(+), 12 deletions(-) create mode 100644 e2e/specs/jsx.spec.ts diff --git a/docs/docs/languages/jsx.md b/docs/docs/languages/jsx.md index 8ebdc62bb..da26944d4 100644 --- a/docs/docs/languages/jsx.md +++ b/docs/docs/languages/jsx.md @@ -1,3 +1,55 @@ # JSX -TODO... +## Usage + +## Language Info + +### Name + +`name` + +### Aliases + +### Extensions + +### Editor + +`script` + +## Compiler + +### Version + +## Code Formatting + +## Custom Settings + +[Custom settings](../advanced/custom-settings.md) added to the property `name` are passed as a JSON object to the `compiler` during compile. Please check the [documentation](#) for full reference. + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "name": {} +} +``` + +## Helper Methods + +## Live Reload + +## Limitations + +## Example Usage + +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +export const params = {}; + +<LiveCodes template="react"></LiveCodes> + +## Starter Template + +## Links diff --git a/e2e/specs/jsx.spec.ts b/e2e/specs/jsx.spec.ts new file mode 100644 index 000000000..cdd206037 --- /dev/null +++ b/e2e/specs/jsx.spec.ts @@ -0,0 +1,156 @@ +import { expect } from '@playwright/test'; +import { test } from '../test-fixtures'; +import { getLoadedApp } from '../helpers'; + +test.describe('JSX', () => { + test('default export', async ({ page, getTestUrl }) => { + const jsx = `export default () => <h1>Hello, World!</h1>`; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('default export with named exports', async ({ page, getTestUrl }) => { + const jsx = ` + export const name = 'World' + const App = () => <h1>Hello, \{name\}!</h1> + export default App + `; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('default export with style import', async ({ page, getTestUrl }) => { + const jsx = ` + import "./style.css" + const App = () => <h1>Hello, World!</h1> + export default App + `; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('default export with style default import', async ({ page, getTestUrl }) => { + const jsx = ` + import styles from "./style.css" + const App = () => <h1>{styles}</h1> + export default App + `; + const css = `h1 { color: red; }`; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx), css: encodeURIComponent(css) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('red'); + }); + + test('no default export', async ({ page, getTestUrl }) => { + const jsx = ` + import React from "react"; + import { createRoot } from "react-dom/client"; + + function App(props) { + return <h1>Hello, {props.name}!</h1>; + } + + const root = createRoot(document.querySelector("#app")); + root.render(<App name="World" />); + `; + const html = '<div id="app"></div>'; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx), html: encodeURIComponent(html) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('no default export & no react import', async ({ page, getTestUrl }) => { + const jsx = ` + import { createRoot } from "react-dom/client"; + + function App(props) { + return <h1>Hello, {props.name}!</h1>; + } + + const root = createRoot(document.querySelector("#app")); + root.render(<App name="World" />); + `; + const html = '<div id="app"></div>'; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx), html: encodeURIComponent(html) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('add to markup', async ({ page, getTestUrl }) => { + const jsx = `export default () => <h1>Hello, World!</h1>`; + const html = '<div>hi</div>'; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx), html: encodeURIComponent(html) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('add to specific element', async ({ page, getTestUrl }) => { + const jsx = `export default () => <h1>Hello, World!</h1>`; + const html = '<div id="livecodes-app"></div>'; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx), html: encodeURIComponent(html) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + await getResult().waitForSelector('h1'); + expect(await getResult().innerText('#livecodes-app')).toContain('Hello, World!'); + }); + + test('import default export in markup', async ({ page, getTestUrl }) => { + const jsx = `export default \`Hello, World!\``; + const html = ` + <script type="module"> + import text from './script'; + const h1 = document.createElement('h1'); + h1.textContent = text; + document.body.appendChild(h1); + </script> + `; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx), html: encodeURIComponent(html) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); + + test('custom runtime', async ({ page, getTestUrl }) => { + const jsx = `/** @jsx h */ + import { h, render } from 'preact'; + function App(props) { + return <h1>Hello, {props.name}!</h1>; + } + render(<App name="World" />, document.body); + `; + + await page.goto(getTestUrl({ jsx: encodeURIComponent(jsx) })); + const { getResult, waitForResultUpdate } = await getLoadedApp(page); + await waitForResultUpdate(); + + expect(await getResult().innerText('h1')).toContain('Hello, World!'); + }); +}); diff --git a/src/livecodes/compiler/import-map.ts b/src/livecodes/compiler/import-map.ts index 0ae45311f..f4286e2cd 100644 --- a/src/livecodes/compiler/import-map.ts +++ b/src/livecodes/compiler/import-map.ts @@ -201,9 +201,6 @@ export const removeImports = (code: string, mods: string[]) => return mods.includes(libName) ? '' : statement; }); -export const removeEditorStylesImport = (code: string) => - code.replace(/import\s+["']\.\/style(s)?\.(css|less|sass|scss)["'];?/g, ''); - export const styleimportsPattern = /(?:@import\s+?)((?:".*?")|(?:'.*?')|(?:url\('.*?'\))|(?:url\(".*?"\)))(.*)?;/g; diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 552816572..7ce909fc7 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -7,8 +7,8 @@ import { isModuleScript, removeImports, // avoid default exports conflict - removeEditorStylesImport, hasDefaultExport, + replaceImports, } from '../compiler'; import { cssPresets, getLanguageCompiler, getLanguageExtension } from '../languages'; import { hasCustomJsxRuntime } from '../languages/typescript'; @@ -23,6 +23,7 @@ import { escapeScript, getAbsoluteUrl, isRelativeUrl, + objectFilter, objectMap, toDataUrl, } from '../utils'; @@ -122,10 +123,6 @@ export const createResultPage = async ({ dom.head.appendChild(stylesheet); }); code.script.compiled = removeImports(code.script.compiled, stylesheetImports); - if (hasDefaultExport(code.script.compiled)) { - // avoid default exports conflict - code.script.compiled = removeEditorStylesImport(code.script.compiled); - } // editor styles if (singleFile) { @@ -248,9 +245,6 @@ export const createResultPage = async ({ ...(runTests && !forExport && hasImports(compiledTests) ? createImportMap(compiledTests, config) : {}), - ...(importFromScript || shouldInsertJsxRuntime - ? { './script': toDataUrl(code.script.compiled) } - : {}), ...createCSSModulesImportMap( code.script.compiled, code.style.compiled, @@ -266,8 +260,22 @@ export const createResultPage = async ({ ...compileInfo.imports, }; + const scriptImport = + importFromScript || shouldInsertJsxRuntime + ? { + './script': toDataUrl( + replaceImports( + code.script.compiled, + config, + objectFilter(userImports, (_value, key) => key.startsWith('./')), + ), + ), + } + : {}; + const importMaps = { ...userImports, + ...scriptImport, ...compilerImports, ...(runTests ? testImports : {}), ...config.imports, From aabfdee22aa72dac42b864cd12854a8ce64b2765 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Wed, 24 Jan 2024 12:04:17 +0200 Subject: [PATCH 18/30] improve detecting react jsx --- src/livecodes/languages/jsx/react-runtime.ts | 14 ++++++++++---- .../languages/react-native/react-native-runtime.ts | 12 +++++++----- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/livecodes/languages/jsx/react-runtime.ts b/src/livecodes/languages/jsx/react-runtime.ts index 75eda3f5e..9c358c6a7 100644 --- a/src/livecodes/languages/jsx/react-runtime.ts +++ b/src/livecodes/languages/jsx/react-runtime.ts @@ -1,11 +1,17 @@ +export const checkIsReact = (App: string) => + ` +const removeComments = (src) => src.replace(/\\/\\*[\\s\\S]*?\\*\\/|([^\\\\:]|^)\\/\\/.*$/gm, '$1'); +const isReactComponent = (c) => typeof c === "function" && (/(return|=>)\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(removeComments(String(c))) || Boolean(c.prototype?.isReactComponent)); +if (!isReactComponent(${App})) return; +`.trim(); + export const reactRuntime = ` import { jsx as _jsx } from "react/jsx-runtime"; import { createRoot } from "react-dom/client"; import App from "./script"; (() => { - const isReactComponent = (c) => typeof c === "function" && (/(return|=>)\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype?.isReactComponent)); - if (!isReactComponent(App)) return; - const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div"))); - root.render(_jsx(App, {})); +${checkIsReact('App')} +const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div"))); +root.render(_jsx(App, {})); })(); `; diff --git a/src/livecodes/languages/react-native/react-native-runtime.ts b/src/livecodes/languages/react-native/react-native-runtime.ts index b02593671..184971c0b 100644 --- a/src/livecodes/languages/react-native/react-native-runtime.ts +++ b/src/livecodes/languages/react-native/react-native-runtime.ts @@ -1,11 +1,13 @@ +// eslint-disable-next-line import/no-internal-modules +import { checkIsReact } from '../jsx/react-runtime'; + export const reactNativeRuntime = ` import { AppRegistry } from "react-native"; import App from "./script"; (() => { - const isReactComponent = (c) => typeof c === "function" && (/(return|=>)\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(String(c)) || Boolean(c.prototype?.isReactComponent)); - if (!isReactComponent(App)) return; - const rootTag = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); - AppRegistry.registerComponent("App", () => App); - AppRegistry.runApplication("App", { rootTag }); +${checkIsReact('App')} +const rootTag = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); +AppRegistry.registerComponent("App", () => App); +AppRegistry.runApplication("App", { rootTag }); })(); `; From e0c0fd917d8e6d86b1f6da3a342b242321e50b1f Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Wed, 24 Jan 2024 12:14:47 +0200 Subject: [PATCH 19/30] fix incorrect URLs in type bundling --- src/livecodes/types/bundle-types.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/livecodes/types/bundle-types.ts b/src/livecodes/types/bundle-types.ts index 641eb2f42..c3295a9c5 100644 --- a/src/livecodes/types/bundle-types.ts +++ b/src/livecodes/types/bundle-types.ts @@ -95,10 +95,6 @@ export async function bundle(options: Options): Promise<string> { assert(typeof prefix === 'string', 'option "prefix" must be a string'); assert(separator.length > 0, 'option "separator" must have non-zero length'); - // turn relative paths into absolute paths - const mainFile = main; - // const outFile = calcOutFilePath(out, baseDir); - trace('### settings object passed ###'); traceObject(options); @@ -106,7 +102,7 @@ export async function bundle(options: Options): Promise<string> { trace('main: %s', main); trace('name: %s', exportName); trace('baseDir: %s', baseDir); - trace('mainFile: %s', mainFile); + trace('mainFile: %s', main); trace('externals: %s', externals ? 'yes' : 'no'); trace('exclude: %s', exclude); trace('comments: %s', comments ? 'yes' : 'no'); @@ -133,9 +129,6 @@ export async function bundle(options: Options): Promise<string> { const inSourceTypings = (file: string) => file.startsWith(sourceRoot); // if file reference is a directory assume commonjs index.d.ts trace('source typings (will be included in output if actually used)'); - - // sourceTypings.forEach(file => trace(' - %s ', file)); - trace('excluded typings (will always be excluded from output)'); const fileMap: { [name: string]: Result } = Object.create(null); @@ -148,7 +141,7 @@ export async function bundle(options: Options): Promise<string> { // following all references and imports trace('\n### parse files ###'); - const queue: string[] = [mainFile]; + const queue: string[] = [main]; const queueSeen: { [name: string]: boolean } = Object.create(null); while (queue.length > 0) { @@ -436,7 +429,7 @@ export async function bundle(options: Options): Promise<string> { } function getExpName(file: string) { - if (file === mainFile) { + if (file === main) { return exportName; } return getExpNameRaw(file); @@ -447,7 +440,7 @@ export async function bundle(options: Options): Promise<string> { } function getLibName(ref: string) { - return getExpNameRaw(mainFile) + separator + prefix + separator + ref; + return getExpNameRaw(main) + separator + prefix + separator + ref; } function cleanupName(name: string) { @@ -489,6 +482,16 @@ export async function bundle(options: Options): Promise<string> { importLineRef: [], relativeRef: [], }; + try { + const url = new URL(file); + const mainUrl = new URL(main); + if (url.origin !== mainUrl.origin && url.origin === window.location.origin) { + trace(' X - Invalid URL: %s', file); + throw new Error(); + } + } catch { + return res; + } let response = await fetch(file); if (!response.ok) { // if file is a directory then lets assume commonjs convention of an index file in the given folder From 104777fc245d1e9a298187c43575e01ceb79521d Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Wed, 24 Jan 2024 12:15:08 +0200 Subject: [PATCH 20/30] start jsx docs --- docs/docs/languages/jsx.md | 58 +++++++++++++------ .../templates/starter/jest-react-starter.ts | 4 +- .../templates/starter/react-starter.ts | 4 +- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/docs/docs/languages/jsx.md b/docs/docs/languages/jsx.md index da26944d4..d9b45bdc9 100644 --- a/docs/docs/languages/jsx.md +++ b/docs/docs/languages/jsx.md @@ -2,15 +2,43 @@ ## Usage +### Importing Modules + +### Exports + +### Styles + +#### CSS Processors + +#### Importing Styles + +#### CSS Modules + +#### CSS Frameworks + +[CSS Frameworks](../features/css.md#css-processors) supported in LiveCodes (e.g. [Tailwind CSS](./tailwindcss.md), [UnoCSS](./unocss.md), [WindiCSS](./windicss.md)) are available for use in JSX. Make sure that the required utility is enabled (Style menu or `processors` property of [configuration object](../configuration/configuration-object.md#processors)) and required [directives](https://tailwindcss.com/docs/functions-and-directives#tailwind) are added to the style editor. + +Example: + +#### CSS-in-JS + +### Root Element + +To render the React components to a specific [root](https://react.dev/reference/react-dom/client/createRoot) DOM element use `livecodes-app` as the element ID. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used as the root. + +### Custom JSX Runtimes + +To [mount](https://vuejs.org/api/application.html#app-mount) the application instance to a specific DOM element use `livecodes-app` as the element ID. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used to mount the instance. + ## Language Info ### Name -`name` +`jsx` -### Aliases +### Extension -### Extensions +`.jsx` ### Editor @@ -18,10 +46,14 @@ ## Compiler +[TypeScript compiler](./typescript.md) + ### Version ## Code Formatting +Using [Prettier](https://prettier.io/). + ## Custom Settings [Custom settings](../advanced/custom-settings.md) added to the property `name` are passed as a JSON object to the `compiler` during compile. Please check the [documentation](#) for full reference. @@ -36,20 +68,12 @@ Please note that custom settings should be valid JSON (i.e. functions are not al } ``` -## Helper Methods - -## Live Reload - -## Limitations - -## Example Usage - -import LiveCodes from '../../src/components/LiveCodes.tsx'; - -export const params = {}; - -<LiveCodes template="react"></LiveCodes> - ## Starter Template +https://livecodes.io/?template=react + ## Links + +- [React](https://react.dev/) +- [JSX](https://react.dev/learn/writing-markup-with-jsx) +- [CSS Modules](https://github.com/css-modules/css-modules) diff --git a/src/livecodes/templates/starter/jest-react-starter.ts b/src/livecodes/templates/starter/jest-react-starter.ts index c82c70b23..467e31e24 100644 --- a/src/livecodes/templates/starter/jest-react-starter.ts +++ b/src/livecodes/templates/starter/jest-react-starter.ts @@ -48,9 +48,11 @@ function Counter(props) { ); } -export default function App() { +function App() { return <Counter name="Jest with React" />; } + +export default App; `.trimStart(), }, tests: { diff --git a/src/livecodes/templates/starter/react-starter.ts b/src/livecodes/templates/starter/react-starter.ts index 43fc1bca1..2fcdc82ff 100644 --- a/src/livecodes/templates/starter/react-starter.ts +++ b/src/livecodes/templates/starter/react-starter.ts @@ -46,7 +46,7 @@ function Counter() { ); } -export default function App() { +function App() { return ( <div className="container"> <Greeting name="React" /> @@ -54,6 +54,8 @@ export default function App() { </div> ); } + +export default App; `.trimStart(), }, stylesheets: [], From 0120bc36a80287ae403502039fa4a1d5ef446948 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Wed, 24 Jan 2024 23:14:30 +0200 Subject: [PATCH 21/30] simplify jsx runtime code --- src/livecodes/languages/jsx/react-runtime.ts | 13 ++------- .../react-native/react-native-runtime.ts | 11 +++----- .../languages/solid/solid-runtime.ts | 3 +- .../templates/starter/jest-react-starter.ts | 4 +-- .../templates/starter/react-starter.ts | 28 +++++-------------- .../templates/starter/solid-starter.ts | 24 ++++------------ 6 files changed, 22 insertions(+), 61 deletions(-) diff --git a/src/livecodes/languages/jsx/react-runtime.ts b/src/livecodes/languages/jsx/react-runtime.ts index 9c358c6a7..a0795498e 100644 --- a/src/livecodes/languages/jsx/react-runtime.ts +++ b/src/livecodes/languages/jsx/react-runtime.ts @@ -1,17 +1,10 @@ -export const checkIsReact = (App: string) => - ` -const removeComments = (src) => src.replace(/\\/\\*[\\s\\S]*?\\*\\/|([^\\\\:]|^)\\/\\/.*$/gm, '$1'); -const isReactComponent = (c) => typeof c === "function" && (/(return|=>)\\s+\\(?\\s*(_jsx|React\\.createElement)/g.test(removeComments(String(c))) || Boolean(c.prototype?.isReactComponent)); -if (!isReactComponent(${App})) return; -`.trim(); - export const reactRuntime = ` import { jsx as _jsx } from "react/jsx-runtime"; import { createRoot } from "react-dom/client"; import App from "./script"; (() => { -${checkIsReact('App')} -const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div"))); -root.render(_jsx(App, {})); + if (typeof App !== "function") return; + const root = createRoot(document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div"))); + root.render(_jsx(App, {})); })(); `; diff --git a/src/livecodes/languages/react-native/react-native-runtime.ts b/src/livecodes/languages/react-native/react-native-runtime.ts index 184971c0b..2949bf314 100644 --- a/src/livecodes/languages/react-native/react-native-runtime.ts +++ b/src/livecodes/languages/react-native/react-native-runtime.ts @@ -1,13 +1,10 @@ -// eslint-disable-next-line import/no-internal-modules -import { checkIsReact } from '../jsx/react-runtime'; - export const reactNativeRuntime = ` import { AppRegistry } from "react-native"; import App from "./script"; (() => { -${checkIsReact('App')} -const rootTag = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); -AppRegistry.registerComponent("App", () => App); -AppRegistry.runApplication("App", { rootTag }); + if (typeof App !== "function") return; + const rootTag = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); + AppRegistry.registerComponent("App", () => App); + AppRegistry.runApplication("App", { rootTag }); })(); `; diff --git a/src/livecodes/languages/solid/solid-runtime.ts b/src/livecodes/languages/solid/solid-runtime.ts index 05f5f7fa9..ac75363b6 100644 --- a/src/livecodes/languages/solid/solid-runtime.ts +++ b/src/livecodes/languages/solid/solid-runtime.ts @@ -2,8 +2,7 @@ export const solidRuntime = ` import { render, createComponent } from "solid-js/web"; import App from "./script"; (() => { - const isSolidComponent = (c) => typeof c === "function" && /return\\s+\\(?\\s*function\\s+\\(\\)\\s+{/g.test(String(c)); - if (!isSolidComponent(App)) return; + if (typeof App !== "function") return; const root = document.querySelector("#livecodes-app") || document.body.appendChild(document.createElement("div")); render(() => createComponent(App, {}), root); })(); diff --git a/src/livecodes/templates/starter/jest-react-starter.ts b/src/livecodes/templates/starter/jest-react-starter.ts index 467e31e24..c82c70b23 100644 --- a/src/livecodes/templates/starter/jest-react-starter.ts +++ b/src/livecodes/templates/starter/jest-react-starter.ts @@ -48,11 +48,9 @@ function Counter(props) { ); } -function App() { +export default function App() { return <Counter name="Jest with React" />; } - -export default App; `.trimStart(), }, tests: { diff --git a/src/livecodes/templates/starter/react-starter.ts b/src/livecodes/templates/starter/react-starter.ts index 2fcdc82ff..54dd1f605 100644 --- a/src/livecodes/templates/starter/react-starter.ts +++ b/src/livecodes/templates/starter/react-starter.ts @@ -25,37 +25,23 @@ export const reactStarter: Template = { script: { language: 'jsx', content: ` -import { useState } from "react"; +import { useState } from "react" -function Greeting(props) { +function Counter(props) { + const [count, setCount] = useState(0); return ( - <> + <div className="container"> <h1>Hello, {props.name}!</h1> <img className="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/react.svg" /> - </> - ); -} - -function Counter() { - const [count, setCount] = useState(0); - return ( - <> <p>You clicked {count} times.</p> <button onClick={() => setCount(count + 1)}>Click me</button> - </> - ); -} - -function App() { - return ( - <div className="container"> - <Greeting name="React" /> - <Counter /> </div> ); } -export default App; +export default function App() { + return <Counter name="React" />; +} `.trimStart(), }, stylesheets: [], diff --git a/src/livecodes/templates/starter/solid-starter.ts b/src/livecodes/templates/starter/solid-starter.ts index d2f33e09a..21fcf0f3b 100644 --- a/src/livecodes/templates/starter/solid-starter.ts +++ b/src/livecodes/templates/starter/solid-starter.ts @@ -27,33 +27,21 @@ export const solidStarter: Template = { content: ` import { createSignal } from "solid-js"; -function Greeting(props: { name: string }) { - return ( - <> - <h1>Hello, {props.name}!</h1> - <img className="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/solid.svg" /> - </> - ); -} - -function Counter() { +function Counter(props: { name: string }) { const [count, setCount] = createSignal(0); const increment = () => setCount(count() + 1); return ( - <> + <div className="container"> + <h1>Hello, {props.name}!</h1> + <img className="logo" alt="logo" src="{{ __livecodes_baseUrl__ }}assets/templates/solid.svg" /> <p>You clicked {count()} times.</p> <button onClick={increment}>Click me</button> - </> + </div> ); } export default function App() { - return ( - <div className="container"> - <Greeting name="Solid" /> - <Counter /> - </div> - ); + return <Counter name="Solid" />; } `.trimStart(), }, From 20d5b5b768a0c60c259afb21b62c42c762cdb4d6 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Thu, 25 Jan 2024 15:23:52 +0200 Subject: [PATCH 22/30] feat(Config): allow disabling JSX auto-render from custom settings --- src/livecodes/result/result-page.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index 7ce909fc7..c3cd72016 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -175,6 +175,7 @@ export const createResultPage = async ({ const jsxRuntime = jsxRuntimes[code.script.language] || ''; const shouldInsertJsxRuntime = Object.keys(jsxRuntimes).includes(code.script.language) && + !config.customSettings[code.script.language]?.disableAutoRender && hasDefaultExport(code.script.compiled) && !hasCustomJsxRuntime(code.script.content || '', config) && !importFromScript; From 3d2e5c383bc7efbb290d64ba98b89b3377554d4f Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Thu, 25 Jan 2024 17:27:48 +0200 Subject: [PATCH 23/30] docs: use SDK `getPlaygroundUrl` in docs `RunInLiveCodes` component --- docs/src/components/RunInLiveCodes.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/src/components/RunInLiveCodes.tsx b/docs/src/components/RunInLiveCodes.tsx index 75c3e9de6..f850a99fc 100644 --- a/docs/src/components/RunInLiveCodes.tsx +++ b/docs/src/components/RunInLiveCodes.tsx @@ -3,11 +3,12 @@ import React from 'react'; import BrowserOnly from '@docusaurus/BrowserOnly'; import CodeBlock from '@theme/CodeBlock'; /* eslint-disable import/no-internal-modules */ -import type { EmbedOptions } from '../../../src/sdk'; +import { getPlaygroundUrl, type EmbedOptions } from '../../../src/sdk'; import { appUrl } from '../utils'; export default function RunInLiveCodes(props: { - params: EmbedOptions['params']; + params?: EmbedOptions['params']; + config?: EmbedOptions['config']; code?: string; language?: string; codeTitle?: string; @@ -19,6 +20,7 @@ export default function RunInLiveCodes(props: { }): JSX.Element { const { params, + config, code, language = 'js', codeTitle = '', @@ -28,12 +30,8 @@ export default function RunInLiveCodes(props: { style = {}, className = '', } = props; - const url = new URL(appUrl); - if (typeof params === 'object') { - (Object.keys(params) as string[]).forEach((param) => { - url.searchParams.set(param, String(params[param])); - }); - } + const url = getPlaygroundUrl({ appUrl, params, config }); + return ( <div style={{ marginBottom: '30px', ...style }} className={className}> {code && ( @@ -53,7 +51,7 @@ export default function RunInLiveCodes(props: { }} </BrowserOnly> )} - <a href={url.href} target="_blank" rel="noreferrer"> + <a href={url} target="_blank" rel="noreferrer"> {linkText} <svg width="12" From c63a32ff69b0e4e52a49c18aedc444d8dc3c71d7 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Thu, 25 Jan 2024 22:12:14 +0200 Subject: [PATCH 24/30] fix(Result): remove extra scripts added to detect classes for CSS processors --- src/livecodes/core.ts | 3 ++- src/livecodes/result/result-page.ts | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/livecodes/core.ts b/src/livecodes/core.ts index 27dcc3ae2..01b19faff 100644 --- a/src/livecodes/core.ts +++ b/src/livecodes/core.ts @@ -866,7 +866,8 @@ const getResultPage = async ({ const compileResults = await Promise.all([ compiler.compile(styleContent, styleLanguage, config, { - html: `${compiledMarkup}<script>${compiledScript}</script><script>${compileInfo.importedContent}</script>`, + html: `${compiledMarkup}<script type="script-for-styles">${compiledScript}</script> + <script type="script-for-styles">${compileInfo.importedContent}</script>`, forceCompile: forceCompileStyles, }), runTests diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index c3cd72016..cbeebb50a 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -142,6 +142,10 @@ export const createResultPage = async ({ const markup = code.markup.compiled; dom.body.innerHTML += markup; + // cleanup extra scripts added to detect classes for CSS processors + const extra = dom.querySelectorAll('script[type="script-for-styles"]'); + extra.forEach((el) => el.remove()); + // cleanup custom configurations and scripts if (code.script.language === 'blockly') { const extra = dom.querySelectorAll( From c77980a1079f09827c8cde44cbf38cf1c3199d58 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Thu, 25 Jan 2024 22:44:29 +0200 Subject: [PATCH 25/30] fix(Result): fix removing/ignoring stylesheet imports in scripts --- src/livecodes/result/result-page.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/livecodes/result/result-page.ts b/src/livecodes/result/result-page.ts index cbeebb50a..fb9dde7d6 100644 --- a/src/livecodes/result/result-page.ts +++ b/src/livecodes/result/result-page.ts @@ -5,8 +5,6 @@ import { getImports, hasImports, isModuleScript, - removeImports, - // avoid default exports conflict hasDefaultExport, replaceImports, } from '../compiler'; @@ -122,7 +120,6 @@ export const createResultPage = async ({ stylesheet.href = url; dom.head.appendChild(stylesheet); }); - code.script.compiled = removeImports(code.script.compiled, stylesheetImports); // editor styles if (singleFile) { @@ -250,6 +247,13 @@ export const createResultPage = async ({ ...(runTests && !forExport && hasImports(compiledTests) ? createImportMap(compiledTests, config) : {}), + ...stylesheetImports.reduce( + (acc, url) => ({ + ...acc, + [url]: toDataUrl(''), + }), + {}, + ), ...createCSSModulesImportMap( code.script.compiled, code.style.compiled, From e87fbf85aa9fa165d1d455e71f7122be7d8fb3c0 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 26 Jan 2024 08:03:31 +0200 Subject: [PATCH 26/30] docs(compilers): add docs for JSX support --- docs/docs/languages/jsx.md | 178 ++++++++++++++++++++++++++++++++++--- docs/docs/languages/vue.md | 6 +- 2 files changed, 169 insertions(+), 15 deletions(-) diff --git a/docs/docs/languages/jsx.md b/docs/docs/languages/jsx.md index d9b45bdc9..a80a4a104 100644 --- a/docs/docs/languages/jsx.md +++ b/docs/docs/languages/jsx.md @@ -1,34 +1,186 @@ # JSX +import LiveCodes from '../../src/components/LiveCodes.tsx'; +import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx'; + +[JSX](https://react.dev/learn/writing-markup-with-jsx) is a syntax extension for JavaScript that allows writing HTML-like markup inside JavaScript. +It has been popularized by [React](https://react.dev/), and then adopted by many other libraries/frameworks. + +By default, when running JSX in LiveCodes, [React](https://react.dev/) runtime is used. +However, other libraries like [Preact](https://preactjs.com/), [nano JSX](https://nanojsx.io/) and others can be used as well (see [Custom JSX Runtimes](#custom-jsx-runtimes)). + +Please note that TSX is also supported in LiveCodes and is [documented here](./tsx.md). + +## Demo: + +<LiveCodes template="react" height="400px"></LiveCodes> + ## Usage +The easiest way is to [auto-render](#auto-rendering) a component by exporting it as the [default export](https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#using_the_default_export): + +export const basicJsxDemo = { jsx: `export default function App() {\n return <h1>Hello World!</h1>;\n}`} + +<RunInLiveCodes params={basicJsxDemo} code={basicJsxDemo.jsx} language="jsx" formatCode={false}></RunInLiveCodes> + +You may, however, be more explicit and render the component yourself using [React DOM](https://react.dev/reference/react-dom/client): + +export const reactDomDemo = { jsx: `import { createRoot } from "react-dom/client";\n\nfunction App() {\n return <h1>Hello World!</h1>;\n}\n\nconst root = createRoot(document.querySelector("#root"));\nroot.render(<App />);`, html: `<div id="root"></div>`} + +<RunInLiveCodes params={reactDomDemo} code={reactDomDemo.jsx} language="jsx" formatCode={false}></RunInLiveCodes> + +:::info note + +React's [new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) is utilized. So there is no need to import React. + +```jsx +// this is not needed: +// import React from 'react'; + +export default function App() { + return <h1>Hello World!</h1>; +} +``` + +::: + +### Auto-rendering + +A component is rendered automatically as a React component (without having to manually use React Dom to render it) if the following conditions are met: + +- The component is exported as the default export. +- No custom JSX runtime is used (see [Custom JSX Runtimes](#custom-jsx-runtimes)). +- No [imports from `"./script"`](#exports) in markup editor. +- Auto-rendering is not [disabled](#disabling-auto-rendering). + +#### Root Element + +To render the React components to a specific [root](https://react.dev/reference/react-dom/client/createRoot) DOM element use `"livecodes-app"` as the element `id`. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used as the root. + +Example: + +export const rootDemo = { html: `<div id="livecodes-app">Loading...</div>`, jsx: `export default function App() {\n return <h1>Hello World!</h1>;\n}`} + +<RunInLiveCodes params={rootDemo} code={rootDemo.html} language="html" formatCode={false}></RunInLiveCodes> + +#### Disabling Auto-rendering + +To disable auto-rendering, set the [custom settings](#custom-settings) `disableAutoRender` property to `true`. + +export const disableAutoRenderDemo = {markup: {language: "html", content: `JSX auto-rendering is disabled. Set from app menu → Custom Settings.`}, script: {language: "jsx", content: `export default function App() {\n return <h1>Hello World!</h1>;\n}`}, customSettings: {"jsx": {"disableAutoRender": true}}} + +<RunInLiveCodes config={disableAutoRenderDemo} code={JSON.stringify(disableAutoRenderDemo.customSettings, null, 2)} language="json" codeTitle="Custom Settings" formatCode={false}></RunInLiveCodes> + ### Importing Modules +npm modules can be imported as described in the section about [module resolution](../features/module-resolution.md), including bare module imports and importing from different CDNs. Stylesheet imports are added as `<link rel="stylesheet">` tags in the page `head`. + +Example: + +export const importsDemo = { jsx: `import { useState, useEffect } from "react";\nimport confetti from "canvas-confetti";\nimport "bootstrap/dist/css/bootstrap.css";\n\nexport default function App() {\n const [count, setCount] = useState(0);\n\n useEffect(() => {\n if (count > 0) {\n confetti();\n }\n }, [count]);\n\n return (\n <div className="m-5 text-center">\n <p>You clicked {count} times.</p>\n <button onClick={() => setCount(count + 1)}>Click me</button>\n </div>\n );\n}\n` } + +<RunInLiveCodes params={importsDemo} code={importsDemo.jsx} language="jsx" formatCode={false}></RunInLiveCodes> + +Module imports can be customized using import maps as described in [module resolution](../features/module-resolution.md#custom-module-resolution) documentations. + +#### Types for Imported Modules + +Types for imported modules are loaded automatically (if available) to provide [Intellisense](../features/intellisense.md), auto-completion and type information. + +![LiveCodes Intellisense](../../static/img/screenshots/intellisense1.jpg) + +![LiveCodes Intellisense](../../static/img/screenshots/intellisense2.jpg) + +Moreover, you can provide custom type definitions for modules that do not have types available on npm. See [Custom Types](../features/intellisense.md#custom-types) for details. + ### Exports +Values exported from script editor (default or named) can be imported in the markup editor by importing from `"./script"` (with no extension). + +This can be useful, for example, when using [MDX](./mdx.md) to import components exported form JSX. + +Demo: + +export const exportsDemo = { mdx: `import Greeting from "./script";\n\n<Greeting name="MDX" />\n`, jsx: `export default function(props) {\n return <h1>Greeting from {props.name}!</h1>;\n}\n` } + +<LiveCodes params={exportsDemo}></LiveCodes> + +:::info note + +When values are imported from `"./script"`, [auto-rendering](#auto-rendering) is disabled, because it is assumed that you want to take control over component rendering. + +::: + ### Styles -#### CSS Processors +CSS can be applied to the component using various ways: + +#### Style Editor + +Styles added in the style editor is applied globally to the [result page](../features/result.md). This can use different **languages/processors** supported in LiveCodes including CSS, SCSS, Less, Stylus, ..etc. See [style documentation](../features/css.md) for more details. + +And of course, styles and stylesheets added in markup editor are also applied globally. + +#### Importing Stylesheets + +Stylesheets imported in script editor are added as `<link rel="stylesheet">` tags in the page `head`. +The stylesheet URL can be an absolute URL or a path in the npm package. The URL has to end with `".css"`. + +example: -#### Importing Styles +export const stylesDemo = { jsx: `import "bootstrap/dist/css/bootstrap.css";\n\nexport default () => <h1 className="m-5 text-center">Hello World!</h1>;\n` } + +<RunInLiveCodes params={stylesDemo} code={stylesDemo.jsx} language="jsx" formatCode={false}></RunInLiveCodes> #### CSS Modules +CSS modules are supported and are [documented separately](./cssmodules.md). Make sure to enable CSS modules (from style editor menu or in [`processors`](../configuration/configuration-object.md#processors) property of [configuration object](../configuration/configuration-object.md)). + +Demo: + +export const cssModulesDemo = { activeEditor: 'script' , style: {language: 'css', content: `.title {\n color: green;\n font-family: sans-serif;\n}\n`}, script: {language: 'jsx', content: `import classes from './style.module.css';\n\nexport default function() {\n return <h1 className={classes.title}>Hello, CSS Modules!</h1>;\n}\n`}, processors: ['cssmodules'] } + +<LiveCodes config={cssModulesDemo}></LiveCodes> + #### CSS Frameworks -[CSS Frameworks](../features/css.md#css-processors) supported in LiveCodes (e.g. [Tailwind CSS](./tailwindcss.md), [UnoCSS](./unocss.md), [WindiCSS](./windicss.md)) are available for use in JSX. Make sure that the required utility is enabled (Style menu or `processors` property of [configuration object](../configuration/configuration-object.md#processors)) and required [directives](https://tailwindcss.com/docs/functions-and-directives#tailwind) are added to the style editor. +[CSS Frameworks](../features/css.md#css-processors) supported in LiveCodes (e.g. [Tailwind CSS](./tailwindcss.md), [UnoCSS](./unocss.md), [WindiCSS](./windicss.md)) can detect class names added in JSX. Make sure that the required utility is enabled (from style editor menu or in [`processors`](../configuration/configuration-object.md#processors) property of [configuration object](../configuration/configuration-object.md)) and that required [directives](https://tailwindcss.com/docs/functions-and-directives#tailwind) are added to the style editor. -Example: +Demo: + +export const tailwindcssDemo = { activeEditor: 'script' , style: {language: 'css', content: `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n`}, script: {language: 'jsx', content: `export default function() {\n return <h1 className="text-3xl font-bold text-gray-500 text-center m-4">Hello, Tailwind CSS!</h1>;\n}\n`}, processors: ['tailwindcss'] } + +<LiveCodes config={tailwindcssDemo}></LiveCodes> #### CSS-in-JS -### Root Element +CSS-in-JS libraries can be imported and used as usual. + +Demo: + +export const styledComponentsDemo = { jsx: `import styled from 'styled-components';\n\nconst Title = styled.h1\`\n text-align: center;\n font-family: sans-serif;\n color: palevioletred;\n\`;\n\nexport default function () {\n return <Title>Hello, styled-components!</Title>;\n}\n` } -To render the React components to a specific [root](https://react.dev/reference/react-dom/client/createRoot) DOM element use `livecodes-app` as the element ID. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used as the root. +<LiveCodes params={styledComponentsDemo}></LiveCodes> ### Custom JSX Runtimes -To [mount](https://vuejs.org/api/application.html#app-mount) the application instance to a specific DOM element use `livecodes-app` as the element ID. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used to mount the instance. +LiveCodes allows using other libraries (like [Preact](https://preactjs.com/) and [nano JSX](https://nanojsx.io/)) as the JSX runtime. + +JSX is compiled to JavaScript using the TypeScript compiler, which allows multiple configuration options for JSX, including [`jsx`](https://www.typescriptlang.org/tsconfig#jsx), [`jsxFactory`](https://www.typescriptlang.org/tsconfig#jsxFactory), [`jsxFragmentFactory`](https://www.typescriptlang.org/tsconfig#jsxFragmentFactory) and [`jsxImportSource`](https://www.typescriptlang.org/tsconfig#jsxImportSource). + +These can be configured using in-code pragmas or in [custom settings](#custom-settings). + +Example for using Preact: + +export const preactDemo = { jsx: `/** @jsx h */\nimport { h, render } from 'preact';\n\nconst App = (props) => <h1>Hello, {props.name}</h1>;\n\nrender(<App name="Preact" />, document.body);\n` } + +<RunInLiveCodes params={preactDemo} code={'//highlight-next-line\n' + preactDemo.jsx} language="jsx" formatCode={false} showLineNumbers={true}></RunInLiveCodes> + +:::info note + +[Auto-rendering](#auto-rendering) is disabled for custom JSX runtimes. + +::: ## Language Info @@ -48,15 +200,14 @@ To [mount](https://vuejs.org/api/application.html#app-mount) the application ins [TypeScript compiler](./typescript.md) -### Version - ## Code Formatting Using [Prettier](https://prettier.io/). ## Custom Settings -[Custom settings](../advanced/custom-settings.md) added to the property `name` are passed as a JSON object to the `compiler` during compile. Please check the [documentation](#) for full reference. +[Custom settings](../advanced/custom-settings.md) added to the property `jsx` are passed to the TypeScript compiler as [compiler options](https://www.typescriptlang.org/tsconfig#compilerOptions) while compiling JSX. +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](#auto-rendering). Please note that custom settings should be valid JSON (i.e. functions are not allowed). @@ -64,7 +215,11 @@ Please note that custom settings should be valid JSON (i.e. functions are not al ```json title="Custom Settings" { - "name": {} + "jsx": { + "disableAutoRender": true, + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment" + } } ``` @@ -76,4 +231,3 @@ https://livecodes.io/?template=react - [React](https://react.dev/) - [JSX](https://react.dev/learn/writing-markup-with-jsx) -- [CSS Modules](https://github.com/css-modules/css-modules) diff --git a/docs/docs/languages/vue.md b/docs/docs/languages/vue.md index 759e1904b..64ae100d5 100644 --- a/docs/docs/languages/vue.md +++ b/docs/docs/languages/vue.md @@ -37,7 +37,7 @@ export const cssModulesDemo = { vue: `<template>\n <p :class="$style.red">This ### CSS Frameworks -[CSS Frameworks](../features/css.md#css-processors) supported in LiveCodes (e.g. [Tailwind CSS](./tailwindcss.md), [UnoCSS](./unocss.md), [WindiCSS](./windicss.md)) are available for use in Vue SFCs. Make sure that the required utility is enabled (Style menu or `processors` property of [configuration object](../configuration/configuration-object.md#processors)) and required [directives](https://tailwindcss.com/docs/functions-and-directives#tailwind) are added to the style editor. +[CSS Frameworks](../features/css.md#css-processors) supported in LiveCodes (e.g. [Tailwind CSS](./tailwindcss.md), [UnoCSS](./unocss.md), [WindiCSS](./windicss.md)) can detect class names added in Vue SFCs. Make sure that the required utility is enabled (from style editor menu or in `processors` property of [configuration object](../configuration/configuration-object.md#processors)) and that required [directives](https://tailwindcss.com/docs/functions-and-directives#tailwind) are added to the style editor. See [example below](#importing-vue-sfcs). @@ -82,7 +82,7 @@ The imported sources can use any of the supported languages/pre-processors (iden ### Module Imports -Modules can be imported as described in the section about [module resolution](../features/module-resolution.md), including bare module imports and importing from different CDNs. Stylesheets imported in the `script` block are added as `<link rel="stylesheet">` tags in the page `head`. +npm modules can be imported as described in the section about [module resolution](../features/module-resolution.md), including bare module imports and importing from different CDNs. Stylesheets imported in the `script` block are added as `<link rel="stylesheet">` tags in the page `head`. Example: @@ -121,7 +121,7 @@ This is an example of importing a Vue SFC, which in turn imports other Vue SFCs ### Root Element -To [mount](https://vuejs.org/api/application.html#app-mount) the application instance to a specific DOM element use `livecodes-app` as the element ID. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used to mount the instance. +To [mount](https://vuejs.org/api/application.html#app-mount) the application instance to a specific DOM element use `"livecodes-app"` as the element `id`. Otherwise, if that element is not found, a new `div` element is added to `document.body` and is used to mount the instance. ## Language Info From 9fa8998902128b5f3e0658b6954ed6ea28f739cd Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 26 Jan 2024 08:13:53 +0200 Subject: [PATCH 27/30] docs(compilers): add docs for TSX support --- docs/docs/languages/tsx.md | 61 +++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/docs/docs/languages/tsx.md b/docs/docs/languages/tsx.md index 4daabfaf2..0fc6f7fe7 100644 --- a/docs/docs/languages/tsx.md +++ b/docs/docs/languages/tsx.md @@ -1,3 +1,62 @@ # TSX -TODO... +import LiveCodes from '../../src/components/LiveCodes.tsx'; +import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx'; + +TSX is a syntax that allows using TypeScript in JSX. +[JSX](https://react.dev/learn/writing-markup-with-jsx) is a syntax extension for JavaScript that allows writing HTML-like markup inside JavaScript. +It has been popularized by [React](https://react.dev/), and then adopted by many other libraries/frameworks. + +By default, when running JSX/TSX in LiveCodes, [React](https://react.dev/) runtime is used. +However, other libraries like [Preact](https://preactjs.com/), [nano JSX](https://nanojsx.io/) and others can be used as well (see [Custom JSX Runtimes](./jsx.md#custom-jsx-runtimes)). + +## Usage + +For usage and examples, see documentation for [JSX](./jsx.md) and [TypeScript](./typescript.md) support in LiveCodes. + +## Language Info + +### Name + +`tsx` + +### Extension + +`.tsx` + +### Editor + +`script` + +## Compiler + +[TypeScript compiler](./typescript.md) + +## Code Formatting + +Using [Prettier](https://prettier.io/). + +## Custom Settings + +[Custom settings](../advanced/custom-settings.md) added to the property `tsx` are passed to the TypeScript compiler as [compiler options](https://www.typescriptlang.org/tsconfig#compilerOptions) while compiling TSX. +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](#auto-rendering). + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "tsx": { + "disableAutoRender": true, + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment" + } +} +``` + +## Links + +- [React](https://react.dev/) +- [JSX](https://react.dev/learn/writing-markup-with-jsx) +- [TypeScript](https://www.typescriptlang.org/) From dfe7c4298c672b36e20da174cd468fadc053a76f Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 26 Jan 2024 08:46:33 +0200 Subject: [PATCH 28/30] docs(compilers): add docs for SolidJS --- docs/docs/languages/solid.md | 68 +++++++++++++++++++++++++++++++- docs/docs/languages/solid.tsx.md | 68 +++++++++++++++++++++++++++++++- docs/docs/languages/tsx.md | 9 +++-- 3 files changed, 139 insertions(+), 6 deletions(-) diff --git a/docs/docs/languages/solid.md b/docs/docs/languages/solid.md index 6d7b6cfbb..012939ed3 100644 --- a/docs/docs/languages/solid.md +++ b/docs/docs/languages/solid.md @@ -1,3 +1,69 @@ # Solid -TODO... +import LiveCodes from '../../src/components/LiveCodes.tsx'; +import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx'; + +[Solid](https://www.solidjs.com/) is a JavaScript framework for making interactive web applications. + +Solid offers very similar syntax to [React](https://react.dev/), with strong focus on [reactivity](https://www.solidjs.com/guides/reactivity) using signals. Solid supports templating in 3 forms [JSX](./jsx.md), Tagged Template Literals and Solid's HyperScript variant, although JSX is the predominate form. Solid also supports [TypeScript](./typescript.md) (See [Solid-TS](./solid.tsx.md)). + +## Demo + +<LiveCodes template="solid" height="400px"></LiveCodes> + +## Usage + +For usage, see documentation for [JSX](./jsx.md) and [TypeScript](./typescript.md) support in LiveCodes. + +## Language Info + +### Name + +`solid` + +### Extension + +`solid.jsx` + +### Editor + +`script` + +## Compiler + +[Official Solid JSX compiler](https://github.com/ryansolid/dom-expressions/tree/main/packages/babel-plugin-jsx-dom-expressions) (`babel-preset-solid`) + +### Version + +`babel-preset-solid` version 1.7.4 + +## Code Formatting + +Using [Prettier](https://prettier.io/). + +## Custom Settings + +[Custom settings](../advanced/custom-settings.md) added to the property `solid` are passed to the Babel compiler during compile. Please check the [documentation](https://github.com/ryansolid/dom-expressions/tree/main/packages/babel-plugin-jsx-dom-expressions#plugin-options) for full reference. +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](./jsx#auto-rendering). + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "solid": { + "disableAutoRender": true + } +} +``` + +## Starter Template + +https://livecodes.io/?template=solid (uses TSX) + +## Links + +- [Solid](https://www.solidjs.com/) +- [JSX](https://react.dev/learn/writing-markup-with-jsx) +- [TypeScript](https://www.typescriptlang.org/) diff --git a/docs/docs/languages/solid.tsx.md b/docs/docs/languages/solid.tsx.md index b5b063978..d3ae327ca 100644 --- a/docs/docs/languages/solid.tsx.md +++ b/docs/docs/languages/solid.tsx.md @@ -1,3 +1,69 @@ # Solid (TS) -TODO... +import LiveCodes from '../../src/components/LiveCodes.tsx'; +import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx'; + +[Solid](https://www.solidjs.com/) is a JavaScript framework for making interactive web applications. + +Solid offers very similar syntax to [React](https://react.dev/), with strong focus on [reactivity](https://www.solidjs.com/guides/reactivity) using signals. Solid supports templating in 3 forms [JSX](./jsx.md), Tagged Template Literals and Solid's HyperScript variant, although JSX is the predominate form. Solid also supports [TypeScript](./typescript.md). + +## Demo + +<LiveCodes template="solid" height="400px"></LiveCodes> + +## Usage + +For usage, see documentation for [JSX](./jsx.md) and [TypeScript](./typescript.md) support in LiveCodes. + +## Language Info + +### Name + +`solid.tsx` + +### Extension + +`solid.tsx` + +### Editor + +`script` + +## Compiler + +[Official Solid JSX compiler](https://github.com/ryansolid/dom-expressions/tree/main/packages/babel-plugin-jsx-dom-expressions) (`babel-preset-solid`) + +### Version + +`babel-preset-solid` version 1.7.4 + +## Code Formatting + +Using [Prettier](https://prettier.io/). + +## Custom Settings + +[Custom settings](../advanced/custom-settings.md) added to the property `solid.tsx` are passed to the Babel compiler during compile. Please check the [documentation](https://github.com/ryansolid/dom-expressions/tree/main/packages/babel-plugin-jsx-dom-expressions#plugin-options) for full reference. +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](./jsx#auto-rendering). + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "solid": { + "disableAutoRender": true + } +} +``` + +## Starter Template + +https://livecodes.io/?template=solid + +## Links + +- [Solid](https://www.solidjs.com/) +- [JSX](https://react.dev/learn/writing-markup-with-jsx) +- [TypeScript](https://www.typescriptlang.org/) diff --git a/docs/docs/languages/tsx.md b/docs/docs/languages/tsx.md index 0fc6f7fe7..7682f6725 100644 --- a/docs/docs/languages/tsx.md +++ b/docs/docs/languages/tsx.md @@ -1,8 +1,5 @@ # TSX -import LiveCodes from '../../src/components/LiveCodes.tsx'; -import RunInLiveCodes from '../../src/components/RunInLiveCodes.tsx'; - TSX is a syntax that allows using TypeScript in JSX. [JSX](https://react.dev/learn/writing-markup-with-jsx) is a syntax extension for JavaScript that allows writing HTML-like markup inside JavaScript. It has been popularized by [React](https://react.dev/), and then adopted by many other libraries/frameworks. @@ -39,7 +36,7 @@ Using [Prettier](https://prettier.io/). ## Custom Settings [Custom settings](../advanced/custom-settings.md) added to the property `tsx` are passed to the TypeScript compiler as [compiler options](https://www.typescriptlang.org/tsconfig#compilerOptions) while compiling TSX. -In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](#auto-rendering). +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](./jsx#auto-rendering). Please note that custom settings should be valid JSON (i.e. functions are not allowed). @@ -55,6 +52,10 @@ Please note that custom settings should be valid JSON (i.e. functions are not al } ``` +## Starter Template + +https://livecodes.io/?template=react (uses JSX) + ## Links - [React](https://react.dev/) From 0bf80a5d8576862ebac93c2c53471837ee1045f5 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 26 Jan 2024 09:14:11 +0200 Subject: [PATCH 29/30] feat(UI): add links to docs in language info --- src/livecodes/html/language-info.html | 203 +++++++++++++++++++------- 1 file changed, 154 insertions(+), 49 deletions(-) diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html index 14a55da1e..475075609 100644 --- a/src/livecodes/html/language-info.html +++ b/src/livecodes/html/language-info.html @@ -332,6 +332,11 @@ <h3>doT.js</h3> <li> <a href="https://olado.github.io/doT/" target="_blank" rel="noopener">Official website</a> </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/dot" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="ejs"> @@ -339,6 +344,11 @@ <h3>EJS</h3> <div>Embedded JavaScript templating.</div> <ul> <li><a href="https://ejs.co/" target="_blank" rel="noopener">Official website</a></li> + <li> + <a href="{{DOCS_BASE_URL}}languages/ejs" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="eta"> @@ -352,6 +362,11 @@ <h3>Eta</h3> <li> <a href="https://eta.js.org/docs/learn" target="_blank" rel="noopener">Documentation</a> </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/eta" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="fennel"> @@ -369,6 +384,11 @@ <h3>Fennel</h3> >Getting Started with Fennel</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/fennel" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=fennel" target="_parent" data-template="fennel">Load starter template</a> </li> @@ -409,29 +429,6 @@ <h3>Go</h3> <li><a href="?template=go" target="_parent" data-template="go">Load starter template</a></li> </ul> </section> -<section data-lang="gnuplot"> - <h3>Gnuplot</h3> - <div>Gnuplot is a portable command-line driven graphing utility.</div> - <div>Here, it is running in the browser using gnuplot-JS.</div> - <ul> - <li> - <a href="http://www.gnuplot.info/" target="_blank" rel="noopener">Gnuplot official website</a> - </li> - <li> - <a href="http://www.gnuplot.info/documentation.html" target="_blank" rel="noopener" - >Gnuplot documentation</a - > - </li> - <li> - <a href="https://github.com/chhu/gnuplot-JS" target="_blank" rel="noopener" - >gnuplot-JS repo</a - > - </li> - <li> - <a href="?template=gnuplot" target="_parent" data-template="gnuplot">Load starter template</a> - </li> - </ul> -</section> <section data-lang="haml"> <h3>Haml</h3> <div>Haml compiler for client side javascript view templates using clientside-haml-js.</div> @@ -450,6 +447,11 @@ <h3>Haml</h3> >Learn X in Y minutes, where X=haml</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/haml" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="handlebars"> @@ -457,6 +459,11 @@ <h3>Handlebars</h3> <div>Minimal templating on steroids.</div> <ul> <li><a href="https://handlebarsjs.com/" target="_blank" rel="noopener">Official website</a></li> + <li> + <a href="{{DOCS_BASE_URL}}languages/handlebars" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="imba"> @@ -470,20 +477,20 @@ <h3>Imba</h3> <h3>JSX</h3> <div> JSX is compiled to JavaScript in LiveCodes using the TypeScript Compiler. <br /> - By default it uses <code>React.createElement</code> + By default it uses React as the JSX runtime. </div> <ul> <li> - <a href="https://reactjs.org/" target="_blank" rel="noopener">React official website</a> + <a href="https://react.dev/" target="_blank" rel="noopener">React official website</a> </li> <li> - <a href="https://reactjs.org/docs/getting-started.html" target="_blank" rel="noopener" - >React documentation</a + <a href="https://react.dev/learn/writing-markup-with-jsx" target="_blank" rel="noopener" + >JSX in React documentation</a > </li> <li> - <a href="https://reactjs.org/docs/introducing-jsx.html" target="_blank" rel="noopener" - >JSX in React documentation</a + <a href="{{DOCS_BASE_URL}}languages/jsx" target="_blank" rel="noopener" + >LiveCodes Documentations</a > </li> <li> @@ -545,6 +552,11 @@ <h3>LiquidJS</h3> >LiquidJS documentation</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/liquid" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="livescript"> @@ -591,6 +603,11 @@ <h3>Lua</h3> >Learn X in Y minutes, where X=Lua</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/lua" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li><a href="?template=lua" target="_parent" data-template="lua">Load starter template</a></li> </ul> </section> @@ -617,6 +634,11 @@ <h3>Lua (Wasm)</h3> >Learn X in Y minutes, where X=Lua</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/lua-wasm" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=lua-wasm" target="_parent" data-template="lua-wasm" >Load starter template</a @@ -689,6 +711,11 @@ <h3>MJML</h3> <li> <a href="https://mjml.io/templates" target="_blank" rel="noopener">MJML official templates</a> </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/mjml" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="mustache"> @@ -708,6 +735,11 @@ <h3>Mustache</h3> >JavaScript implementation</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/mustache" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="nunjucks"> @@ -722,6 +754,11 @@ <h3>Nunjucks</h3> >Official website</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/nunjucks" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="twig"> @@ -749,6 +786,11 @@ <h3>Twig</h3> >Twig.js Documentation</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/twig" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="ocaml"> @@ -822,6 +864,11 @@ <h3>PHP</h3> >Learn X in Y minutes, where X=PHP</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/php" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li><a href="?template=php" target="_parent" data-template="php">Load starter template</a></li> </ul> </section> @@ -843,6 +890,11 @@ <h3>PHP (Wasm)</h3> >Learn X in Y minutes, where X=PHP</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/php-wasm" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=php-wasm" target="_parent" data-template="php-wasm" >Load starter template</a @@ -891,6 +943,11 @@ <h3>Pug</h3> >Learn X in Y minutes, where X=Pug</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/pug" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="python-wasm"> @@ -913,6 +970,11 @@ <h3>Python (Wasm)</h3> >Learn X in Y minutes, where X=Python</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/python-wasm" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=python-wasm" target="_parent" data-template="python-wasm" >Load starter template</a @@ -938,6 +1000,11 @@ <h3>Python</h3> >Learn X in Y minutes, where X=Python</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/python" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=python" target="_parent" data-template="python">Load starter template</a> </li> @@ -945,7 +1012,6 @@ <h3>Python</h3> </section> <section data-lang="r"> <h3>R</h3> - <div class="description">(R language support in LiveCodes is still experimental)</div> <div>R running in the browser using WebR.</div> <ul> <li> @@ -971,6 +1037,11 @@ <h3>R</h3> >Learn X in Y minutes, where X=R</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/r" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=r" target="_parent" data-template="r">Load starter template</a> </li> @@ -1154,6 +1225,11 @@ <h3>Ruby</h3> >Learn X in Y minutes, where X=ruby</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/ruby" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=ruby" target="_parent" data-template="ruby">Load starter template</a> </li> @@ -1186,6 +1262,11 @@ <h3>Ruby (WASM)</h3> >Learn X in Y minutes, where X=ruby</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/ruby-wasm" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=ruby-wasm" target="_parent" data-template="ruby-wasm" >Load starter template</a @@ -1287,10 +1368,13 @@ <h3>Solid (with TypeScript)</h3> > </li> <li> - <a href="?template=solid" target="_parent" data-template="solid" - >Load starter template (JSX)</a + <a href="{{DOCS_BASE_URL}}languages/solid.tsx" target="_blank" rel="noopener" + >LiveCodes Documentations</a > </li> + <li> + <a href="?template=solid" target="_parent" data-template="solid">Load starter template</a> + </li> </ul> </section> <section data-lang="solid"> @@ -1300,7 +1384,14 @@ <h3>Solid</h3> <li><a href="https://www.solidjs.com/" target="_blank" rel="noopener">Official website</a></li> <li><a href="https://www.solidjs.com/docs" target="_blank" rel="noopener">Documentation</a></li> <li> - <a href="?template=solid" target="_parent" data-template="solid">Load starter template</a> + <a href="{{DOCS_BASE_URL}}languages/solid" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> + <li> + <a href="?template=solid" target="_parent" data-template="solid" + >Load starter template (TSX)</a + > </li> </ul> </section> @@ -1386,6 +1477,11 @@ <h3>Sucrase</h3> >Sucrase GitHub Repo</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/sucrase" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="svelte"> @@ -1448,6 +1544,11 @@ <h3>Teal</h3> >Teal tutorial</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/teal" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=teal" target="_parent" data-template="teal">Load starter template</a> </li> @@ -1457,19 +1558,14 @@ <h3>Teal</h3> <h3>TSX</h3> <div> TypeScript in JSX. TSX is compiled to JavaScript in LiveCodes using the TypeScript Compiler.<br /> - By default it uses <code>React.createElement</code> + By default it uses React as the JSX runtime. </div> <ul> <li> - <a href="https://reactjs.org/" target="_blank" rel="noopener">React official website</a> - </li> - <li> - <a href="https://reactjs.org/docs/getting-started.html" target="_blank" rel="noopener" - >React documentation</a - > + <a href="https://react.dev/" target="_blank" rel="noopener">React official website</a> </li> <li> - <a href="https://reactjs.org/docs/introducing-jsx.html" target="_blank" rel="noopener" + <a href="https://react.dev/learn/writing-markup-with-jsx" target="_blank" rel="noopener" >JSX in React documentation</a > </li> @@ -1478,6 +1574,11 @@ <h3>TSX</h3> >Typescript documentation</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/tsx" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="typescript"> @@ -1506,24 +1607,23 @@ <h3>TypeScript</h3> </section> <section data-lang="vue"> <h3>Vue3 Single File Components</h3> - <div>Loaded using vue3-sfc-loader.</div> <ul> <li> - <a href="https://v3.vuejs.org/" target="_blank" rel="noopener">Vue.js v3 official website</a> + <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue.js v3 official website</a> </li> <li> - <a href="https://v3.vuejs.org/guide/introduction.html" target="_blank" rel="noopener" + <a href="https://vuejs.org/guide/introduction.html" target="_blank" rel="noopener" >Vue3 documentation</a > </li> <li> - <a href="https://v3.vuejs.org/guide/single-file-component.html" target="_blank" rel="noopener" + <a href="https://vuejs.org/api/sfc-spec.html" target="_blank" rel="noopener" >Vue3 single file components</a > </li> <li> - <a href="https://github.com/FranckFreiburger/vue3-sfc-loader" target="_blank" rel="noopener" - >vue3-sfc-loader GitHub repo</a + <a href="{{DOCS_BASE_URL}}languages/vue" target="_blank" rel="noopener" + >LiveCodes Documentations</a > </li> <li><a href="?template=vue" target="_parent" data-template="vue">Load starter template</a></li> @@ -1535,11 +1635,11 @@ <h3>Vue2 Single File Components</h3> <ul> <li><a href="https://vuejs.org/" target="_blank" rel="noopener">Vue.js official website</a></li> <li> - <a href="https://vuejs.org/v2/guide/" target="_blank" rel="noopener">Vue2 documentation</a> + <a href="https://v2.vuejs.org/v2/guide/" target="_blank" rel="noopener">Vue2 documentation</a> </li> <li> <a - href="https://vuejs.org/v2/guide/single-file-components.html" + href="https://v2.vuejs.org/v2/guide/single-file-components.html" target="_blank" rel="noopener" >Vue2 single file components</a @@ -1550,6 +1650,11 @@ <h3>Vue2 Single File Components</h3> >vue3-sfc-loader GitHub repo</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/vue2" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> </ul> </section> <section data-lang="wat"> From 35f644d5e064303877fabed796792e06c8942c17 Mon Sep 17 00:00:00 2001 From: Hatem Hosny <hatemhosny@gmail.com> Date: Fri, 26 Jan 2024 09:52:05 +0200 Subject: [PATCH 30/30] docs(compilers): add docs for react-native --- docs/docs/languages/react-native-tsx.md | 63 +++++++++++++++++++++++- docs/docs/languages/react-native.md | 65 ++++++++++++++++++++++++- src/livecodes/html/language-info.html | 10 ++++ 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/docs/docs/languages/react-native-tsx.md b/docs/docs/languages/react-native-tsx.md index 6ab61d298..3cb6e5dc1 100644 --- a/docs/docs/languages/react-native-tsx.md +++ b/docs/docs/languages/react-native-tsx.md @@ -1,3 +1,64 @@ # React Native (TSX) -TODO... +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +[React Native](https://reactnative.dev/) is a framework for building mobile apps using React and React Native. React Native support in LiveCodes is achieved by using [React Native for Web](https://necolas.github.io/react-native-web/) (an accessible implementation of React Native's Components and APIs that is interoperable with React DOM). + +## Demo + +<LiveCodes template="react-native" height="400px"></LiveCodes> + +## Usage + +For usage and examples, see documentation for [JSX](./jsx.md). + +## Language Info + +### Name + +`react-native-tsx` + +### Extension + +`.react-native.tsx` + +### Editor + +`script` + +## Compiler + +[TypeScript compiler](./typescript.md) and [React Native for Web](https://necolas.github.io/react-native-web/) + +## Code Formatting + +Using [Prettier](https://prettier.io/). + +## Custom Settings + +[Custom settings](../advanced/custom-settings.md) added to the property `react-native-tsx` are passed to the TypeScript compiler as [compiler options](https://www.typescriptlang.org/tsconfig#compilerOptions) while compiling TSX. +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](./jsx#auto-rendering). + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "react-native-tsx": { + "disableAutoRender": true + } +} +``` + +## Starter Template + +https://livecodes.io/?template=react-native (uses JSX) + +## Links + +- [React Native](https://reactnative.dev/) +- [React Native for Web](https://necolas.github.io/react-native-web/) +- [React](https://react.dev/) +- [JSX](https://react.dev/learn/writing-markup-with-jsx) +- [TypeScript](https://www.typescriptlang.org/) diff --git a/docs/docs/languages/react-native.md b/docs/docs/languages/react-native.md index a38482129..fbb231622 100644 --- a/docs/docs/languages/react-native.md +++ b/docs/docs/languages/react-native.md @@ -1,3 +1,66 @@ # React Native -TODO... +import LiveCodes from '../../src/components/LiveCodes.tsx'; + +[React Native](https://reactnative.dev/) is a framework for building mobile apps using React and React Native. React Native support in LiveCodes is achieved by using [React Native for Web](https://necolas.github.io/react-native-web/) (an accessible implementation of React Native's Components and APIs that is interoperable with React DOM). + +TypeScript is also supported in React Native (TSX) and is [documented here](./react-native-tsx.md). + +## Demo + +<LiveCodes template="react-native" height="400px"></LiveCodes> + +## Usage + +For usage and examples, see documentation for [JSX](./jsx.md). + +## Language Info + +### Name + +`react-native` + +### Extension + +`.react-native.jsx` + +### Editor + +`script` + +## Compiler + +[TypeScript compiler](./typescript.md) and [React Native for Web](https://necolas.github.io/react-native-web/) + +## Code Formatting + +Using [Prettier](https://prettier.io/). + +## Custom Settings + +[Custom settings](../advanced/custom-settings.md) added to the property `react-native` are passed to the TypeScript compiler as [compiler options](https://www.typescriptlang.org/tsconfig#compilerOptions) while compiling JSX. +In addition, the option `disableAutoRender` can be set to `true` to disable [auto-rendering](./jsx#auto-rendering). + +Please note that custom settings should be valid JSON (i.e. functions are not allowed). + +**Example:** + +```json title="Custom Settings" +{ + "react-native": { + "disableAutoRender": true + } +} +``` + +## Starter Template + +https://livecodes.io/?template=react-native + +## Links + +- [React Native](https://reactnative.dev/) +- [React Native for Web](https://necolas.github.io/react-native-web/) +- [React](https://react.dev/) +- [JSX](https://react.dev/learn/writing-markup-with-jsx) +- [TypeScript](https://www.typescriptlang.org/) diff --git a/src/livecodes/html/language-info.html b/src/livecodes/html/language-info.html index 475075609..0baf2767e 100644 --- a/src/livecodes/html/language-info.html +++ b/src/livecodes/html/language-info.html @@ -1080,6 +1080,11 @@ <h3>React Native for Web (with TypeScript)</h3> >TypeScript documentation</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/react-native-tsx" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=react-native" target="_parent" data-template="react-native" >Load starter template (JSX)</a @@ -1110,6 +1115,11 @@ <h3>React Native for Web</h3> >React Native documentation</a > </li> + <li> + <a href="{{DOCS_BASE_URL}}languages/react-native" target="_blank" rel="noopener" + >LiveCodes Documentations</a + > + </li> <li> <a href="?template=react-native" target="_parent" data-template="react-native" >Load starter template</a