https://github.com/vgulerianb/react-exe
A powerful React component executor that renders code with external dependencies and custom styling
https://github.com/vgulerianb/react-exe
artifacts claude javascript nextjs playground react runtime v0
Last synced: about 1 month ago
JSON representation
A powerful React component executor that renders code with external dependencies and custom styling
- Host: GitHub
- URL: https://github.com/vgulerianb/react-exe
- Owner: vgulerianb
- Created: 2025-02-04T18:59:56.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2025-02-26T11:28:49.000Z (4 months ago)
- Last Synced: 2025-04-17T12:32:08.549Z (2 months ago)
- Topics: artifacts, claude, javascript, nextjs, playground, react, runtime, v0
- Language: TypeScript
- Homepage: https://react-exe-demo.vercel.app/
- Size: 12.2 MB
- Stars: 164
- Watchers: 6
- Forks: 12
- Open Issues: 3
-
Metadata Files:
- Readme: Readme.md
Awesome Lists containing this project
- awesome - vgulerianb/react-exe - A powerful React component executor that renders code with external dependencies and custom styling (TypeScript)
README
# React-EXE
Execute React components on the fly with external dependencies, custom styling, and TypeScript support. Perfect for creating live code previews, documentation, or interactive code playgrounds.
Try the live demo [here](https://react-exe-demo.vercel.app/).
## Features
- 🚀 Execute React components from string code
- 📦 Support for external dependencies
- 🎨 Tailwind CSS support
- 🔒 Built-in security checks
- 💅 Customizable styling
- 📝 TypeScript support
- ⚡ Live rendering
- 🐛 Error boundary protection
- 📄 Multi-file support## Installation
```bash
npm install react-exe
# or
yarn add react-exe
# or
pnpm add react-exe
```## Basic Usage
```tsx
import { CodeExecutor } from "react-exe";const code = `
export default function HelloWorld() {
return (
Hello World!
);
}
`;function App() {
return ;
}
```## Advanced Usage
### With External Dependencies
```tsx
import { CodeExecutor } from "react-exe";
import * as echarts from "echarts";
import * as framerMotion from "framer-motion";const code = `
import { motion } from 'framer-motion';
import { LineChart } from 'echarts';export default function Dashboard() {
return (
);
}
`;function App() {
return (
);
}
```### With absolute imports and wildcard patterns
```tsx
import { CodeExecutor } from "react-exe";
import * as echarts from "echarts";
import * as framerMotion from "framer-motion";
import * as uiComponents from "../ShadcnComps";const code = `
import { motion } from 'framer-motion';
import { LineChart } from 'echarts';
import { Button } from "@/components/ui/button"export default function Dashboard() {
return (
);
}
`;function App() {
return (
);
}
```### With Multiple Files
React-EXE supports multiple files with cross-imports, allowing you to build more complex components and applications:
```tsx
import { CodeExecutor } from "react-exe";
import * as framerMotion from "framer-motion";// Define multiple files as an array of code files
const files = [
{
name: "App.tsx", // Main entry file
content: `
import React from 'react';
import { motion } from 'framer-motion';
import Header from './Header';
import Counter from './Counter';const App = () => {
return (
);
};export default App;
`,
isEntry: true, // Mark this as the entry point
},
{
name: "Header.tsx",
content: `
import React from 'react';interface HeaderProps {
title: string;
}const Header = ({ title }: HeaderProps) => {
return (
{title}
);
};export default Header;
`,
},
{
name: "Counter.tsx",
content: `
import React, { useState } from 'react';
import { motion } from 'framer-motion';
import CounterButton from './CounterButton';const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
return (
Counter Component
{count}
);
};export default Counter;
`,
},
{
name: "CounterButton.tsx",
content: `
import React from 'react';
import { motion } from 'framer-motion';interface CounterButtonProps {
onClick: () => void;
label: string;
variant?: 'primary' | 'success' | 'danger';
}const CounterButton = ({
onClick,
label,
variant = 'primary'
}: CounterButtonProps) => {
const getButtonColor = () => {
switch(variant) {
case 'success': return 'bg-green-500 hover:bg-green-600';
case 'danger': return 'bg-red-500 hover:bg-red-600';
default: return 'bg-blue-500 hover:bg-blue-600';
}
};
return (
{label}
);
};export default CounterButton;
`,
},
];function App() {
return (
);
}
```### Creating a Project Structure with Multiple Files
For more complex applications, you can organize your files in a project-like structure:
```tsx
import { CodeExecutor } from "react-exe";
import * as reactRouter from "react-router-dom";
import * as framerMotion from "framer-motion";const files = [
{
name: "App.tsx",
content: `
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';const App = () => {
return (
}>
} />
} />
} />
);
};export default App;
`,
isEntry: true,
},
{
name: "components/Layout.tsx",
content: `
import React from 'react';
import { Outlet } from 'react-router-dom';
import Navbar from './Navbar';
import Footer from './Footer';const Layout = () => {
return (
);
};export default Layout;
`,
},
{
name: "components/Navbar.tsx",
content: `
import React from 'react';
import { Link, useLocation } from 'react-router-dom';const Navbar = () => {
const location = useLocation();
const isActive = (path: string) => {
return location.pathname === path ?
'text-white bg-indigo-700' :
'text-indigo-200 hover:text-white hover:bg-indigo-600';
};
return (
Multi-File App
Home
About
);
};export default Navbar;
`,
},
{
name: "components/Footer.tsx",
content: `
import React from 'react';const Footer = () => {
return (
© {new Date().getFullYear()} React-EXE Demo
Built with multiple files
);
};export default Footer;
`,
},
{
name: "pages/Home.tsx",
content: `
import React from 'react';
import { motion } from 'framer-motion';const Home = () => {
return (
Welcome to the Home Page
This is a multi-file application example using React-EXE.
It demonstrates how you can create complex applications with multiple
components, pages, and even routing!
Features Demonstrated:
- Multiple file structure
- React Router integration
- Animation with Framer Motion
- Component composition
- Styling with Tailwind CSS
);
};export default Home;
`,
},
{
name: "pages/About.tsx",
content: `
import React from 'react';
import { motion } from 'framer-motion';const About = () => {
return (
About Page
React-EXE is a powerful library for executing React components on the fly.
It supports multi-file applications like this one!
{[1, 2, 3].map((item) => (
Feature {item}
This is an example of a card that demonstrates Framer Motion animations
in a multi-file React component.
))}
);
};export default About;
`,
},
{
name: "pages/NotFound.tsx",
content: `
import React from 'react';
import { Link } from 'react-router-dom';
import { motion } from 'framer-motion';const NotFound = () => {
return (
404
Page Not Found
The page you're looking for doesn't exist or has been moved.
Return Home
);
};export default NotFound;
`,
},
];function App() {
return (
);
}
```### Using Custom Hooks and Utilities in Multi-File Apps
You can also create and use custom hooks, utilities, and TypeScript types across multiple files:
```tsx
import { CodeExecutor } from "react-exe";const files = [
{
name: "App.tsx",
content: `
import React from 'react';
import ThemeProvider from './theme/ThemeProvider';
import ThemeSwitcher from './components/ThemeSwitcher';
import UserProfile from './components/UserProfile';
import { fetchUserData } from './utils/api';const App = () => {
return (
);
};export default App;
`,
isEntry: true,
},
{
name: "types/index.ts",
content: `
export interface User {
id: string;
name: string;
email: string;
avatar: string;
}export type Theme = 'light' | 'dark' | 'system';
export interface ThemeContextType {
theme: Theme;
setTheme: (theme: Theme) => void;
}
`,
},
{
name: "theme/ThemeProvider.tsx",
content: `
import React, { createContext, useContext, useState, useEffect } from 'react';
import { Theme, ThemeContextType } from '../types';const ThemeContext = createContext(undefined);
const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState('system');
useEffect(() => {
const applyTheme = (newTheme: Theme) => {
const root = window.document.documentElement;
// Remove any existing theme classes
root.classList.remove('light', 'dark');
// Apply the appropriate theme
if (newTheme === 'system') {
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
root.classList.add(systemTheme);
} else {
root.classList.add(newTheme);
}
};
applyTheme(theme);
// Listen for system theme changes
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = () => {
if (theme === 'system') {
applyTheme('system');
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [theme]);
return (
{children}
);
};export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};export default ThemeProvider;
`,
},
{
name: "components/ThemeSwitcher.tsx",
content: `
import React from 'react';
import { useTheme } from '../theme/ThemeProvider';
import { Theme } from '../types';const ThemeSwitcher = () => {
const { theme, setTheme } = useTheme();
const themes: { value: Theme; label: string }[] = [
{ value: 'light', label: '☀️ Light' },
{ value: 'dark', label: '🌙 Dark' },
{ value: 'system', label: '🖥️ System' }
];
return (
{themes.map(({ value, label }) => (
setTheme(value)}
className={\`px-3 py-1 rounded-md \${
theme === value
? 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200'
: 'hover:bg-gray-100 dark:hover:bg-gray-700'
}\`}
>
{label}
))}
);
};export default ThemeSwitcher;
`,
},
{
name: "hooks/useUser.ts",
content: `
import { useState, useEffect } from 'react';
import { User } from '../types';export const useUser = (
userId: string,
fetchUserData: (id: string) => Promise
) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
const loadUser = async () => {
try {
setLoading(true);
const userData = await fetchUserData(userId);
if (isMounted) {
setUser(userData);
setError(null);
}
} catch (err) {
if (isMounted) {
setError('Failed to load user');
setUser(null);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
loadUser();
return () => {
isMounted = false;
};
}, [userId, fetchUserData]);
return { user, loading, error };
};
`,
},
{
name: "utils/api.ts",
content: `
import { User } from '../types';// Simulate API call with mock data
export const fetchUserData = async (userId: string): Promise => {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 1000));
// Mock data
const users: Record = {
'1': {
id: '1',
name: 'John Doe',
email: '[email protected]',
avatar: 'https://randomuser.me/api/portraits/men/32.jpg'
},
'2': {
id: '2',
name: 'Jane Smith',
email: '[email protected]',
avatar: 'https://randomuser.me/api/portraits/women/44.jpg'
}
};
const user = users[userId];
if (!user) {
throw new Error(\`User with ID \${userId} not found\`);
}
return user;
};
`,
},
{
name: "components/UserProfile.tsx",
content: `
import React from 'react';
import { useUser } from '../hooks/useUser';
import { User } from '../types';interface UserProfileProps {
userId: string;
fetchUserData: (id: string) => Promise;
}const UserProfile = ({ userId, fetchUserData }: UserProfileProps) => {
const { user, loading, error } = useUser(userId, fetchUserData);
if (loading) {
return (
);
}
if (error) {
return (
{error}
);
}
if (!user) {
returnNo user found;
}
return (
![]()
{user.name}
{user.email}
User ID: {user.id}
);
};export default UserProfile;
`,
},
];function App() {
return (
);
}
```### With Custom Error Handling
```tsx
import { CodeExecutor } from "react-exe";function App() {
return (
{
console.error("Component error:", error);
// Send to error tracking service
trackError(error);
},
// Custom security patterns
securityPatterns: [
/localStorage/i,
/sessionStorage/i,
/window\.location/i,
],
}}
/>
);
}
```## Configuration Options
The `config` prop accepts the following options:
```typescript
interface CodeExecutorConfig {
// External dependencies available to the rendered component
dependencies?: Record;// Enable Tailwind CSS support
enableTailwind?: boolean;// Custom className for the container
containerClassName?: string;// Custom inline styles for the container
containerStyle?: React.CSSProperties;// Custom className for error messages
errorClassName?: string;// Custom inline styles for error messages
errorStyle?: React.CSSProperties;// Custom security patterns to block potentially malicious code
securityPatterns?: RegExp[];// Error callback function
onError?: (error: Error) => void;
}
```## Code Input Types
React-EXE accepts code in two formats:
1. **Single File**: Pass a string containing the React component code
```typescript
// Single file as a string
const code = `
export default function App() {
returnHello World;
}
`;
```2. **Multiple Files**: Pass an array of CodeFile objects:
```typescript
// Multiple files
const code = [
{
name: "App.tsx",
content:
"import React from 'react';\nimport Button from './Button';\n...",
isEntry: true, // Mark this as the entry point
},
{
name: "Button.tsx",
content:
"export default function Button() { return Click me; }",
},
];
```The `CodeFile` interface:
```typescript
interface CodeFile {
name: string; // File name with extension (used for imports)
content: string; // File content
isEntry?: boolean; // Whether this is the entry point (defaults to first file if not specified)
}
```## Security
React-EXE includes built-in security measures:
- Default security patterns to block potentially harmful code
- Custom security pattern support
- Error boundary protectionDefault blocked patterns include:
```typescript
const defaultSecurityPatterns = [
/document\.cookie/i,
/window\.document\.cookie/i,
/eval\(/i,
/Function\(/i,
/document\.write/i,
/document\.location/i,
];
```## TypeScript Support
React-EXE is written in TypeScript and includes type definitions. For the best development experience, use TypeScript in your project:
```tsx
import { CodeExecutor, CodeExecutorConfig, CodeFile } from "react-exe";const config: CodeExecutorConfig = {
enableTailwind: true,
dependencies: {
"my-component": MyComponent,
},
};const files: CodeFile[] = [
{
name: "App.tsx",
content: `export default function App() { returnHello; }`,
isEntry: true,
},
];function App() {
return ;
}
```## Used By [TuneChat](https://chat.tune.app/) to render Artifacts
## License
MIT © [Vikrant](https://www.linkedin.com/in/vikrant-guleria/)
---
Made with ❤️ by [Vikrant](https://www.linkedin.com/in/vikrant-guleria/)