(src: string)
| 98 | } |
| 99 | |
| 100 | function reactHarness(src: string): string { |
| 101 | const code = stripModuleSyntax(src); |
| 102 | const comp = detectReactComponent(code); |
| 103 | const head = |
| 104 | `<script crossorigin src="${CDN.react}"></script>` + |
| 105 | `<script crossorigin src="${CDN.reactDom}"></script>` + |
| 106 | `<script src="${CDN.babel}"></script>`; |
| 107 | |
| 108 | // We expose the common hooks as bare names (useState, …) for convenience. But if the model's |
| 109 | // OWN code already declares any of them — e.g. `const { useState } = React;` or a leftover |
| 110 | // `import { useState }` that became a declaration — injecting our destructure too causes |
| 111 | // "Identifier 'useState' has already been declared" and the whole render dies. So we only |
| 112 | // declare the hooks the model didn't already bring into scope itself. |
| 113 | const ALL_HOOKS = ['useState', 'useEffect', 'useRef', 'useMemo', 'useCallback', 'useReducer', 'useContext', 'useLayoutEffect']; |
| 114 | const declares = (name: string): boolean => { |
| 115 | // `const { … useState … } = React` OR `const useState = …` OR `function useState` |
| 116 | const inDestructure = new RegExp(`(?:const|let|var)\\s*\\{[^}]*\\b${name}\\b[^}]*\\}\\s*=`).test(code); |
| 117 | const asBinding = new RegExp(`(?:const|let|var|function)\\s+${name}\\b`).test(code); |
| 118 | return inDestructure || asBinding; |
| 119 | }; |
| 120 | const hooksToInject = ALL_HOOKS.filter(h => !declares(h)); |
| 121 | const hookLine = hooksToInject.length |
| 122 | ? `const { ${hooksToInject.join(', ')} } = React;\n` |
| 123 | : ''; |
| 124 | |
| 125 | // The component source + a bootstrap, both transpiled by Babel-in-the-browser. |
| 126 | // Mount priority: an `export default` (captured as window.__art_default) wins, else the |
| 127 | // detected named component, else a global named `App`. This covers named components, |
| 128 | // `export default function Foo`, and anonymous `export default () => …`. |
| 129 | const body = |
| 130 | `<div id="__art_root"></div>` + |
| 131 | `<script type="text/babel" data-presets="react">\n` + |
| 132 | hookLine + |
| 133 | `${code}\n` + |
| 134 | `try {\n` + |
| 135 | ` var __Art = (typeof window.__art_default !== 'undefined' && window.__art_default)\n` + |
| 136 | ` || (typeof ${comp} !== 'undefined' ? ${comp} : (typeof App !== 'undefined' ? App : null));\n` + |
| 137 | ` if (!__Art) throw new Error('No React component found to render (expected a component named ${comp} or a default export).');\n` + |
| 138 | ` ReactDOM.createRoot(document.getElementById('__art_root')).render(React.createElement(__Art));\n` + |
| 139 | `} catch (e) {\n` + |
| 140 | ` document.getElementById('__art_root').innerHTML = '<pre class=\\'__art_text\\'>Render error: ' + (e && e.message) + '</pre>';\n` + |
| 141 | `}\n` + |
| 142 | `</script>`; |
| 143 | return htmlShell(body, head); |
| 144 | } |
| 145 | |
| 146 | function vueHarness(src: string): string { |
| 147 | // Use vue3-sfc-loader to render a .vue single-file component straight from source text. |
no test coverage detected