TsxWindow.html•9.34 kB
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>${filename}</title>
<!-- Load local React and ReactDOM -->
<script src="assets://react.development.js"></script>
<script src="assets://react-dom.development.js"></script>
<!-- Load local Babel Standalone -->
<script src="assets://babel.min.js"></script>
<script src="assets://twind.umd.js"></script>
<!-- <link href="styles://output.css" rel="stylesheet">-->
<script src="assets://lucide.js"></script>
</head>
<body>
<div id="root"></div>
<!-- Embed the TSX code in a script that Babel will compile -->
<script id="module-code" type="text/plain">
${tsxCode}
</script>
<script>
async function processTailwindJIT(componentCode) {
try {
// Get the twind objects from the window
const twind = window.twind;
// Create a virtual sheet manually since sheets.virtual() is not available
const sheet = {
target: '',
insert(rule) {
this.target += rule;
}
};
// Configure twind with the virtual sheet
twind.setup({
sheet,
preflight: true, // Include Tailwind's base styles
mode: 'silent', // Don't warn about unknown classes
theme: {
extend: {}
}
});
// Use a regex that matches both static (double-quoted) and template literal (backtick) className values
const regex = /className=(?:"([^"]*)"|\{`([^`]*)`\})/g;
let allClasses = [];
let match;
while ((match = regex.exec(componentCode)) !== null) {
// Use group 1 for static strings or group 2 for template literals
let classStr = match[1] || match[2] || '';
// Extract dynamic tokens from interpolations (e.g. ${...})
let dynamicTokens = [];
const interpolationRegex = /\$\{([^}]+)\}/g;
let dynamicMatch;
while ((dynamicMatch = interpolationRegex.exec(classStr)) !== null) {
let token = dynamicMatch[1].trim();
// Split by whitespace in case the expression yields multiple tokens and remove surrounding quotes from each token
dynamicTokens.push(...token.split(/\s+/).map(tok => tok.replace(/^['"]+|['"]+$/g, '')));
}
// Remove interpolated expressions from the static part
classStr = classStr.replace(/\$\{[^}]+}/g, '').trim();
let staticTokens = classStr ? classStr.split(/\s+/) : [];
// Combine both static and dynamic tokens
const tokens = staticTokens.concat(dynamicTokens);
if (tokens.length > 0) {
allClasses.push(...tokens);
}
}
// Apply each token with twind; ignore any failures
allClasses.forEach(token => {
try {
twind.tw(token);
} catch (e) {
// Ignore tokens that fail to compile
}
});
// Get the CSS content from the virtual sheet
const cssText = sheet.target;
// Return the CSS content
return cssText;
} catch (error) {
console.error('Error processing Twind styles:', error);
return '';
}
}
async function renderDynamicComponent() {
// Get the inline TSX code
const sourceCode = document.getElementById('module-code').textContent;
// Generate JIT CSS for just this component
const componentCSS = await processTailwindJIT(sourceCode);
// Inject the CSS
const styleEl = document.createElement('style');
styleEl.textContent = componentCSS;
document.head.appendChild(styleEl);
// Transform the code using Babel. We use the presets for React and TypeScript.
const { code } = Babel.transform(sourceCode, {
filename: '${filename}', // Provide a filename for Babel to resolve parsing correctly.
presets: [
['env', { modules: 'commonjs' }], // Transforms ES modules to CommonJS.
'react',
'typescript'
],
});
// Create a require function that returns React when asked for it.
const requireFn = (moduleName) => {
if (moduleName === 'react') {
return React;
}
if (moduleName === 'lucide-react') {
const lucideProxy = {};
// Get all icon names from window.lucide
const iconNames = Object.keys(window.lucide).filter(
// Filter out non-array entries or prototype properties
key => Array.isArray(window.lucide[key]) &&
!key.startsWith('__') &&
!key.startsWith('[[Prototype]]')
);
console.log(`Creating ${iconNames.length} Lucide icon components`);
// Create a component for each icon
iconNames.forEach(iconName => {
lucideProxy[iconName] = createSvgIconFromLucideData(iconName, window.lucide[iconName]);
});
return lucideProxy;
}
throw new Error("Module not found: " + moduleName);
};
// Helper function to create an SVG component from Lucide icon data
function createSvgIconFromLucideData(iconName, iconData) {
return React.forwardRef((props, ref) => {
const {
color = 'currentColor',
size = 24,
strokeWidth = 2,
...restProps
} = props;
return React.createElement(
'svg',
{
ref,
xmlns: 'http://www.w3.org/2000/svg',
width: size,
height: size,
viewBox: '0 0 24 24',
fill: 'none',
stroke: color,
strokeWidth,
strokeLinecap: 'round',
strokeLinejoin: 'round',
...restProps
},
// Process each element in the icon data array
...iconData.map((item, index) => {
// Based on the structure we see in the console:
// Each item is an array where first element is the element type (e.g. "path")
// and second element contains attributes
if (Array.isArray(item) && item.length >= 2) {
const [elementType, attributes] = item;
// Create the SVG element with its attributes
return React.createElement(
elementType,
{
key: `${iconName}-${index}`,
...processAttributes(attributes)
}
);
}
// Handle case where item is just a string (e.g. "path")
if (typeof item === 'string') {
return React.createElement(
item,
{ key: `${iconName}-${index}` }
);
}
return null;
})
);
});
}
// Helper to process attributes (handle the various formats seen in the console)
function processAttributes(attrs) {
if (!attrs) return {};
// If attrs is already an object, return it
if (typeof attrs === 'object' && !Array.isArray(attrs)) {
return attrs;
}
// If attrs is an array of key-value pairs like ["path", {...}]
if (Array.isArray(attrs) && attrs.length >= 2 && typeof attrs[1] === 'object') {
return attrs[1];
}
// If attrs is something else, try to make sense of it
const result = {};
// Handle case where we have [key, value] pairs
if (Array.isArray(attrs)) {
for (let i = 0; i < attrs.length; i += 2) {
if (i + 1 < attrs.length) {
const key = attrs[i];
const value = attrs[i + 1];
if (typeof key === 'string') {
result[key] = value;
}
}
}
}
return result;
}
const module = { exports: {} };
const exports = module.exports;
new Function("require", "module", "exports", code)(requireFn, module, exports);
// If there is a default export, use it; otherwise, pick the first export.
const Component =
module.exports.default ||
Object.values(module.exports)[0];
if (!Component) {
console.error("No component was found in the module exports.");
} else {
const rootElement = document.getElementById("root");
if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
// Check if Component is a class or forwardRef component
const isClassComponent = Component.prototype && Component.prototype.isReactComponent;
const isForwardRef = Component.$$typeof === Symbol.for('react.forward_ref');
if (isClassComponent || isForwardRef) {
// For class components and forwardRef components, we can use refs
root.render(
React.createElement('div',
{ id: 'dynamic-component-container' },
React.createElement(Component, {
ref: (instance) => {
window.dynamicComponent = instance;
}
})
)
);
} else {
// For regular function components, don't use refs
const componentElement = React.createElement(Component, {});
window.dynamicComponent = componentElement;
root.render(
React.createElement('div',
{ id: 'dynamic-component-container' },
componentElement
)
);
}
}
}
}
</script>
<script>
// Wait for the DOM to be ready
document.addEventListener('DOMContentLoaded', async () => {
await renderDynamicComponent();
});
</script>
</body>
</html>