Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mrm007 committed Nov 17, 2023
1 parent 74be8e3 commit 785520f
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 42 deletions.
4 changes: 4 additions & 0 deletions @types/esbuild.wasm.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'esbuild-wasm/esbuild.wasm' {
const wasmURL: string;
export default wasmURL;
}
7 changes: 7 additions & 0 deletions lib/makeWebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ module.exports = async (playroomConfig, options) => {
include: path.dirname(require.resolve('codemirror/package.json')),
use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')],
},
{
test: /esbuild.wasm$/,
type: 'asset/resource',
generator: {
filename: 'esbuild-[hash][ext]',
},
},
],
},
optimization: {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"css-loader": "^6.7.2",
"current-git-branch": "^1.1.0",
"dedent": "^0.7.0",
"esbuild-wasm": "^0.19.5",
"fast-glob": "^3.2.12",
"find-up": "^5.0.0",
"fuzzy": "^0.1.3",
Expand All @@ -92,6 +93,7 @@
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"lz-string": "^1.4.4",
"memoize-one": "^6.0.0",
"mini-css-extract-plugin": "^2.7.2",
"parse-prop-types": "^0.3.0",
"polished": "^4.2.2",
Expand Down
17 changes: 15 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions src/Playroom/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
import { formatCode as format, isMac } from '../../utils/formatting';
import {
closeFragmentTag,
compileJsx,
openFragmentTag,
validateCode,
} from '../../utils/compileJsx';

import * as styles from './CodeEditor.css';
Expand Down Expand Up @@ -42,13 +42,13 @@ import {
} from './keymaps/cursors';
import { wrapInTag } from './keymaps/wrap';

const validateCode = (editorInstance: Editor, code: string) => {
const validateCodeInEditor = (editorInstance: Editor, code: string) => {
editorInstance.clearGutter('errorGutter');

try {
compileJsx(code);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '';
const validOrError = validateCode(code);
if (validOrError !== true) {
const errorMessage =
validOrError instanceof Error ? validOrError.message : '';
const matches = errorMessage.match(/\(([0-9]+):/);
const lineNumber =
matches && matches.length >= 2 && matches[1] && parseInt(matches[1], 10);
Expand Down Expand Up @@ -172,7 +172,7 @@ export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => {
}

editorInstanceRef.current.setValue(code);
validateCode(editorInstanceRef.current, code);
validateCodeInEditor(editorInstanceRef.current, code);
}
}, [code, previewCode]);

Expand Down Expand Up @@ -213,12 +213,12 @@ export const CodeEditor = ({ code, onChange, previewCode, hints }: Props) => {
<ReactCodeMirror
editorDidMount={(editorInstance) => {
editorInstanceRef.current = editorInstance;
validateCode(editorInstance, code);
validateCodeInEditor(editorInstance, code);
setCursorPosition(cursorPosition);
}}
onChange={(editorInstance, data, newCode) => {
if (editorInstance.hasFocus() && !previewCode) {
validateCode(editorInstance, newCode);
validateCodeInEditor(editorInstance, newCode);
debouncedChange(newCode);
}
}}
Expand Down
22 changes: 9 additions & 13 deletions src/Playroom/Frames/Frames.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { useEffect, useRef, useState } from 'react';
import flatMap from 'lodash/flatMap';
import Iframe from './Iframe';
import {
compileJsx,
openFragmentTag,
closeFragmentTag,
} from '../../utils/compileJsx';
import { compileJsx } from '../../utils/compileJsx';
import type { PlayroomProps } from '../Playroom';
import { Strong } from '../Strong/Strong';
import { Text } from '../Text/Text';
Expand All @@ -31,16 +27,16 @@ export default function Frames({ code, themes, widths }: FramesProps) {
}))
);

const [renderCode, setRenderCode] = useState(
() => `${openFragmentTag}${closeFragmentTag}`
);
const [renderCode, setRenderCode] = useState('');

useEffect(() => {
try {
const newCode = compileJsx(code);
setRenderCode(newCode);
} catch (e) {}
}, [code]);
(async () => {
try {
const newCode = await compileJsx(code);
setRenderCode(newCode);
} catch (e) {}
})();
});

return (
<div ref={scrollingPanelRef} className={styles.root}>
Expand Down
16 changes: 13 additions & 3 deletions src/Playroom/Preview.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentType, ReactNode } from 'react';
import { useState, type ComponentType, type ReactNode, useEffect } from 'react';
import lzString from 'lz-string';

import { useParams } from '../utils/params';
Expand Down Expand Up @@ -32,13 +32,23 @@ export default ({ themes, components, FrameComponent }: PreviewProps) => {
);

return {
code: compileJsx(result.code),
code: result.code,
themeName: result.theme,
};
}

return {};
});
const [renderCode, setRenderCode] = useState('');

useEffect(() => {
(async () => {
try {
const newCode = await compileJsx(code || '');
setRenderCode(newCode);
} catch (e) {}
})();
});

const resolvedTheme = themeName ? themes[themeName] : null;

Expand All @@ -49,7 +59,7 @@ export default ({ themes, components, FrameComponent }: PreviewProps) => {
themeName={themeName || '__PLAYROOM__NO_THEME__'}
theme={resolvedTheme}
>
<RenderCode code={code} scope={components} />
<RenderCode code={renderCode} scope={components} />
</FrameComponent>
</div>
<div className={styles.splashScreenContainer}>
Expand Down
8 changes: 8 additions & 0 deletions src/Playroom/RenderCode/RenderCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import {
} from '../../utils/compileJsx';

export default function RenderCode({ code, scope }) {
// TODO: move these up
if (ReactCreateElementPragma in scope) {
throw new Error(`${ReactCreateElementPragma} not allowed in scope`);
}
if (ReactFragmentPragma in scope) {
throw new Error(`${ReactFragmentPragma} not allowed in scope`);
}

return scopeEval(code, {
...(useScope() ?? {}),
...scope,
Expand Down
54 changes: 40 additions & 14 deletions src/utils/compileJsx.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
import { transform } from '@babel/standalone';
import memoizeOne from 'memoize-one';
import * as esbuild from 'esbuild-wasm';
import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm';

const init = esbuild.initialize({
wasmURL: `/${esbuildWasmUrl}`,
});

export const ReactFragmentPragma = 'R_F';
export const ReactCreateElementPragma = 'R_cE';

export const openFragmentTag = '<>';
export const closeFragmentTag = '</>';

export const compileJsx = (code: string) =>
transform(`${openFragmentTag}${code.trim()}${closeFragmentTag}`, {
presets: [
[
'react',
{
pragma: ReactCreateElementPragma,
pragmaFrag: ReactFragmentPragma,
},
export const compileJsxWithBabel = memoizeOne((code: string) => {
const result = transform(
`${openFragmentTag}${code.trim()}${closeFragmentTag}`,
{
presets: [
[
'react',
{
pragma: ReactCreateElementPragma,
pragmaFrag: ReactFragmentPragma,
},
],
],
],
}).code;
}
);
return result.code;
});

export const compileJsx = memoizeOne(async (code: string) => {
await init;
const result = await esbuild.transform(
`${openFragmentTag}${code.trim()}${closeFragmentTag}`,
{
loader: 'jsx',
jsxFactory: ReactCreateElementPragma,
jsxFragment: ReactFragmentPragma,
}
);

return result.code;
});

export const validateCode = (code: string) => {
export const validateCode = (code: string): true | Error => {
try {
compileJsx(code);
compileJsxWithBabel(code);
return true;
} catch (err) {
return false;
return err as Error;
}
};
2 changes: 1 addition & 1 deletion src/utils/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ export const isValidLocation = ({
cursor,
snippet: breakoutString,
})
);
) === true;

0 comments on commit 785520f

Please sign in to comment.