From 885d9845fa76ed43e5d08911c27bb73b01d35e30 Mon Sep 17 00:00:00 2001 From: Neo Nie Date: Tue, 11 Jan 2022 00:30:31 +0000 Subject: [PATCH] feat: add createRequire and remove imports --- README.md | 48 +++++++++++++------ packages/react-live-runner/README.md | 48 +++++++++++++------ packages/react-runner/README.md | 48 +++++++++++++------ .../react-runner/src/__tests__/utils.test.ts | 6 +-- packages/react-runner/src/transform.ts | 4 +- packages/react-runner/src/types.ts | 2 - packages/react-runner/src/useRunner.ts | 5 -- packages/react-runner/src/utils.ts | 25 +++++----- website/src/constants.ts | 12 ++++- website/src/pages/index.tsx | 9 +--- website/src/pages/playground.tsx | 4 +- 11 files changed, 129 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index f19b44a..2d7e3f5 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ Run your React code on the go [https://react-runner.vercel.app](https://react-ru - Function component - Class component, **with class fields support** - Composing components with `render` or `export default` -- Support `Typescript` - Server Side Rendering +- Support `Typescript` +- Support `import` statement via `createRequire` utility Hacker News [in react-runner](https://react-runner.vercel.app/#hacker-news) vs [in real world](https://react-runner.vercel.app/examples/hacker-news), with the same code @@ -27,14 +28,23 @@ npm install --save react-runner - **code** `string`, _required_ the code to be ran - **scope** `object` globals that could be used in `code` -- **imports** `object` imports that could be used in `code` + +### Predefined scope + +```js +{ + React, + jsxPragma: React.createElement, // useful if you are using Emotion with `css` prop + jsxFragmentPragma: React.Fragment, +} +``` ## Usage ```jsx import { useRunner } from 'react-runner' -const { element, error } = useRunner({ code, scope, imports }) +const { element, error } = useRunner({ code, scope }) ``` or use `Runner` as a component directly and handle error with `onRendered` @@ -42,16 +52,25 @@ or use `Runner` as a component directly and handle error with `onRendered` ```jsx import { Runner } from 'react-runner' -const element = ( - -) +const element = ``` +### `import` statement + +```js +// you can define your own version of `createRequire` +import { createRequire } from 'react-runner' +import * as YourPkg from 'your-pkg' + +const scope = { + require: createRquire({ + 'your-pkg': YourPkg, + }), +} +``` + +then in your code you can use `import Foo, { Bar } from 'your-pkg'` + ## Browser support ``` @@ -70,7 +89,7 @@ I love it, but I love arrow functions for event handlers instead of bind them ma and I don't want to change my code to be compliant with restrictions, so I created this project, use [Sucrase](https://github.com/alangpierce/sucrase) instead of [Bublé](https://github.com/bublejs/buble) to transpile the code. -If you are using `react-live` in your project and want a smooth transition, `react-live-runner` is there for you which provide the identical way to play with: +If you are using `react-live` in your project and want a smooth transition, `react-live-runner` is there for you which provide the identical way to play with, and `react-live-runner` re-exports `react-runner` so you can use everything in `react-runner` by importing `react-live-runner` ```jsx import { @@ -81,7 +100,7 @@ import { } from 'react-live-runner' ... - + @@ -97,7 +116,6 @@ import { useLiveRunner, CodeEditor } from 'react-live-runner' const { element, error, code, onChange } = useLiveRunner({ initialCode, scope, - imports, transformCode, }) @@ -117,7 +135,7 @@ import { useState, useEffect } from 'react' import { useRunner } from 'react-runner' const [code, onChange] = useState(initialCode) -const { element, error } = useRunner({ code, scope, imports }) +const { element, error } = useRunner({ code, scope }) useEffect(() => { onChange(initialCode) diff --git a/packages/react-live-runner/README.md b/packages/react-live-runner/README.md index f19b44a..2d7e3f5 100644 --- a/packages/react-live-runner/README.md +++ b/packages/react-live-runner/README.md @@ -8,8 +8,9 @@ Run your React code on the go [https://react-runner.vercel.app](https://react-ru - Function component - Class component, **with class fields support** - Composing components with `render` or `export default` -- Support `Typescript` - Server Side Rendering +- Support `Typescript` +- Support `import` statement via `createRequire` utility Hacker News [in react-runner](https://react-runner.vercel.app/#hacker-news) vs [in real world](https://react-runner.vercel.app/examples/hacker-news), with the same code @@ -27,14 +28,23 @@ npm install --save react-runner - **code** `string`, _required_ the code to be ran - **scope** `object` globals that could be used in `code` -- **imports** `object` imports that could be used in `code` + +### Predefined scope + +```js +{ + React, + jsxPragma: React.createElement, // useful if you are using Emotion with `css` prop + jsxFragmentPragma: React.Fragment, +} +``` ## Usage ```jsx import { useRunner } from 'react-runner' -const { element, error } = useRunner({ code, scope, imports }) +const { element, error } = useRunner({ code, scope }) ``` or use `Runner` as a component directly and handle error with `onRendered` @@ -42,16 +52,25 @@ or use `Runner` as a component directly and handle error with `onRendered` ```jsx import { Runner } from 'react-runner' -const element = ( - -) +const element = ``` +### `import` statement + +```js +// you can define your own version of `createRequire` +import { createRequire } from 'react-runner' +import * as YourPkg from 'your-pkg' + +const scope = { + require: createRquire({ + 'your-pkg': YourPkg, + }), +} +``` + +then in your code you can use `import Foo, { Bar } from 'your-pkg'` + ## Browser support ``` @@ -70,7 +89,7 @@ I love it, but I love arrow functions for event handlers instead of bind them ma and I don't want to change my code to be compliant with restrictions, so I created this project, use [Sucrase](https://github.com/alangpierce/sucrase) instead of [Bublé](https://github.com/bublejs/buble) to transpile the code. -If you are using `react-live` in your project and want a smooth transition, `react-live-runner` is there for you which provide the identical way to play with: +If you are using `react-live` in your project and want a smooth transition, `react-live-runner` is there for you which provide the identical way to play with, and `react-live-runner` re-exports `react-runner` so you can use everything in `react-runner` by importing `react-live-runner` ```jsx import { @@ -81,7 +100,7 @@ import { } from 'react-live-runner' ... - + @@ -97,7 +116,6 @@ import { useLiveRunner, CodeEditor } from 'react-live-runner' const { element, error, code, onChange } = useLiveRunner({ initialCode, scope, - imports, transformCode, }) @@ -117,7 +135,7 @@ import { useState, useEffect } from 'react' import { useRunner } from 'react-runner' const [code, onChange] = useState(initialCode) -const { element, error } = useRunner({ code, scope, imports }) +const { element, error } = useRunner({ code, scope }) useEffect(() => { onChange(initialCode) diff --git a/packages/react-runner/README.md b/packages/react-runner/README.md index f19b44a..2d7e3f5 100644 --- a/packages/react-runner/README.md +++ b/packages/react-runner/README.md @@ -8,8 +8,9 @@ Run your React code on the go [https://react-runner.vercel.app](https://react-ru - Function component - Class component, **with class fields support** - Composing components with `render` or `export default` -- Support `Typescript` - Server Side Rendering +- Support `Typescript` +- Support `import` statement via `createRequire` utility Hacker News [in react-runner](https://react-runner.vercel.app/#hacker-news) vs [in real world](https://react-runner.vercel.app/examples/hacker-news), with the same code @@ -27,14 +28,23 @@ npm install --save react-runner - **code** `string`, _required_ the code to be ran - **scope** `object` globals that could be used in `code` -- **imports** `object` imports that could be used in `code` + +### Predefined scope + +```js +{ + React, + jsxPragma: React.createElement, // useful if you are using Emotion with `css` prop + jsxFragmentPragma: React.Fragment, +} +``` ## Usage ```jsx import { useRunner } from 'react-runner' -const { element, error } = useRunner({ code, scope, imports }) +const { element, error } = useRunner({ code, scope }) ``` or use `Runner` as a component directly and handle error with `onRendered` @@ -42,16 +52,25 @@ or use `Runner` as a component directly and handle error with `onRendered` ```jsx import { Runner } from 'react-runner' -const element = ( - -) +const element = ``` +### `import` statement + +```js +// you can define your own version of `createRequire` +import { createRequire } from 'react-runner' +import * as YourPkg from 'your-pkg' + +const scope = { + require: createRquire({ + 'your-pkg': YourPkg, + }), +} +``` + +then in your code you can use `import Foo, { Bar } from 'your-pkg'` + ## Browser support ``` @@ -70,7 +89,7 @@ I love it, but I love arrow functions for event handlers instead of bind them ma and I don't want to change my code to be compliant with restrictions, so I created this project, use [Sucrase](https://github.com/alangpierce/sucrase) instead of [Bublé](https://github.com/bublejs/buble) to transpile the code. -If you are using `react-live` in your project and want a smooth transition, `react-live-runner` is there for you which provide the identical way to play with: +If you are using `react-live` in your project and want a smooth transition, `react-live-runner` is there for you which provide the identical way to play with, and `react-live-runner` re-exports `react-runner` so you can use everything in `react-runner` by importing `react-live-runner` ```jsx import { @@ -81,7 +100,7 @@ import { } from 'react-live-runner' ... - + @@ -97,7 +116,6 @@ import { useLiveRunner, CodeEditor } from 'react-live-runner' const { element, error, code, onChange } = useLiveRunner({ initialCode, scope, - imports, transformCode, }) @@ -117,7 +135,7 @@ import { useState, useEffect } from 'react' import { useRunner } from 'react-runner' const [code, onChange] = useState(initialCode) -const { element, error } = useRunner({ code, scope, imports }) +const { element, error } = useRunner({ code, scope }) useEffect(() => { onChange(initialCode) diff --git a/packages/react-runner/src/__tests__/utils.test.ts b/packages/react-runner/src/__tests__/utils.test.ts index 2884f24..ba7cc2c 100644 --- a/packages/react-runner/src/__tests__/utils.test.ts +++ b/packages/react-runner/src/__tests__/utils.test.ts @@ -1,6 +1,6 @@ import { create } from 'react-test-renderer' -import { generateElement } from '../utils' +import { generateElement, createRequire } from '../utils' test('empty code', () => { const element = generateElement({ code: `` }) @@ -227,7 +227,7 @@ test('imports', () => { const element = generateElement({ code: `import Foo from 'foo' render()`, - imports: { foo: () => 'hello' }, + scope: { require: createRequire({ foo: () => 'hello' }) }, }) expect(element).toMatchInlineSnapshot(``) @@ -239,7 +239,7 @@ test('invalid imports', () => { generateElement({ code: `import Foo from 'foo' render()`, - imports: { bar: () => 'hello' }, + scope: { require: createRequire({ bar: () => 'hello' }) }, }) ).toThrowErrorMatchingInlineSnapshot(`"Module not found: 'foo'"`) }) diff --git a/packages/react-runner/src/transform.ts b/packages/react-runner/src/transform.ts index ec31c30..c2ec593 100644 --- a/packages/react-runner/src/transform.ts +++ b/packages/react-runner/src/transform.ts @@ -1,8 +1,8 @@ import { transform as _transform } from 'sucrase' -export const transform = (code: string, hasImports?: boolean) => { +export const transform = (code: string, transformImports?: boolean) => { return _transform(code, { - transforms: hasImports + transforms: transformImports ? ['jsx', 'typescript', 'imports'] : ['jsx', 'typescript'], production: true, diff --git a/packages/react-runner/src/types.ts b/packages/react-runner/src/types.ts index 69cddda..0b4a715 100644 --- a/packages/react-runner/src/types.ts +++ b/packages/react-runner/src/types.ts @@ -5,6 +5,4 @@ export type RunnerOptions = { code: string /** globals that could be used in code */ scope?: Scope - /** imports that could be used in code */ - imports?: Scope } diff --git a/packages/react-runner/src/useRunner.ts b/packages/react-runner/src/useRunner.ts index 2478ed4..336d1d4 100644 --- a/packages/react-runner/src/useRunner.ts +++ b/packages/react-runner/src/useRunner.ts @@ -16,21 +16,17 @@ export type UseRunnerReturn = { export const useRunner = ({ code, scope, - imports, disableCache, }: UseRunnerProps): UseRunnerReturn => { const isMountRef = useRef(true) const elementRef = useRef(null) const scopeRef = useRef(scope) scopeRef.current = scope - const importsRef = useRef(imports) - importsRef.current = imports const [state, setState] = useState(() => { const element = createElement(Runner, { code, scope: scopeRef.current, - imports: importsRef.current, onRendered: (error) => { if (error) { setState({ @@ -54,7 +50,6 @@ export const useRunner = ({ const element = createElement(Runner, { code, scope: scopeRef.current, - imports: importsRef.current, onRendered: (error) => { if (error) { setState({ diff --git a/packages/react-runner/src/utils.ts b/packages/react-runner/src/utils.ts index fa0b9cb..3e43cc7 100644 --- a/packages/react-runner/src/utils.ts +++ b/packages/react-runner/src/utils.ts @@ -38,26 +38,23 @@ const baseScope = { jsxFragmentPragma: React.Fragment, } +export const createRequire = (imports: Scope) => (module: string) => { + if (!imports.hasOwnProperty(module)) { + throw new Error(`Module not found: '${module}'`) + } + return imports[module] +} + export const generateElement = ( options: RunnerOptions ): ReactElement | null => { - const { code, scope, imports } = options + const { code, scope } = options const trimmedCode = code.trim() if (!trimmedCode) return null - const hasImports = !!imports && Object.keys(imports).length > 0 - const transformedCode = transform(prepareCode(trimmedCode), hasImports) - const evalScope: Scope = { ...baseScope, ...scope } - if (hasImports) { - evalScope.require = (module: string) => { - if (!imports.hasOwnProperty(module)) { - throw new Error(`Module not found: '${module}'`) - } - return imports[module] - } - } - - const result = evalCode(transformedCode, evalScope) + const transformImports = scope?.require && typeof scope.require === 'function' + const transformedCode = transform(prepareCode(trimmedCode), transformImports) + const result = evalCode(transformedCode, { ...baseScope, ...scope }) if (!result) return null if (isValidElement(result)) return result diff --git a/website/src/constants.ts b/website/src/constants.ts index f1ac207..fa6d8f2 100644 --- a/website/src/constants.ts +++ b/website/src/constants.ts @@ -1,12 +1,20 @@ import React from 'react' +import { createRequire } from 'react-live-runner' import styled, { css, keyframes, createGlobalStyle } from 'styled-components' import * as Styled from 'styled-components' import { codeBlock } from 'common-tags' // @ts-ignore import hn from '!!raw-loader!./pages/examples/hacker-news.tsx' -export const scope = { ...React, styled, css, keyframes, createGlobalStyle } -export const imports = { react: React, 'styled-components': Styled } +const imports = { react: React, 'styled-components': Styled } +export const scope = { + ...React, + styled, + css, + keyframes, + createGlobalStyle, + //require: createRequire(imports), +} export const examples = [ { diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx index 2690f1b..1f4da18 100644 --- a/website/src/pages/index.tsx +++ b/website/src/pages/index.tsx @@ -2,7 +2,7 @@ import { Fragment } from 'react' import styled from 'styled-components' import { UseRunner as LiveRunner } from '../components/LiveRunner' -import { scope, imports, examples } from '../constants' +import { scope, examples } from '../constants' const Container = styled.div` max-width: 1024px; @@ -24,12 +24,7 @@ const Page = () => ( {examples.map(({ key, title, code }) => ( {title} - + ))} diff --git a/website/src/pages/playground.tsx b/website/src/pages/playground.tsx index 0a12898..a1b951c 100644 --- a/website/src/pages/playground.tsx +++ b/website/src/pages/playground.tsx @@ -10,7 +10,7 @@ import { Error, } from '../components/LiveRunner' import { getHashCode, updateHash } from '../utils/urlHash' -import { scope, imports } from '../constants' +import { scope } from '../constants' // @ts-ignore import sampleCode from '!!raw-loader!./examples/hacker-news.tsx' @@ -29,7 +29,7 @@ const Container = styled.div` ` const Playground = () => { - const { element, error, code, onChange } = useLiveRunner({ scope, imports }) + const { element, error, code, onChange } = useLiveRunner({ scope }) // reset to clear editing history const [editorKey, resetEditor] = useReducer((state: number) => state + 1, 0)