├── .assetsignore ├── .gitignore ├── README.md ├── examples ├── chat.html ├── demo.html ├── test.html ├── todo.html └── todo.html.md ├── experiments ├── babel.html ├── index.html ├── simple-react.html └── ssreact-multi.html ├── index.html ├── min.html ├── min.js ├── playground.html ├── src ├── bundle.html ├── bundle.js ├── generate-all.js ├── microjsx.js ├── microjsx2.js ├── ssreact.js └── test.html └── wrangler.toml /.assetsignore: -------------------------------------------------------------------------------- 1 | .wrangler 2 | .git 3 | .DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .wrangler -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stupid Simple React 2 | 3 | ssreact uses [preact](https://preactjs.com) and [a tiny JSX compiler](https://github.com/mllev/micro-jsx) and some scaffolding to make it possible to run react directly in HTML with a single script import. The bundle is just 21KB minified, small enough to use in production if you ask me. 4 | 5 | # Use cases 6 | 7 | Use it for quickly crafting static websites that need to render some complex react stuff but you don't want to set up whole react thing. Works great together with LLMs and deployment on Cloudflare Workers! 8 | 9 | I don't know how well this scales yet. 10 | 11 | Now you can create React components, anywhere, as long as div id matches `ssreact.{YourComponentName}` and you use `type="text/babel"` in your scripts that define code: 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 35 | 36 | 37 | ``` 38 | 39 | # Issues 40 | 41 | MicroJSX has a tiny problem with spaces. Because of this, we need to use `{" "}` after variable names, e.g. `Clicked {count} times`. Furthermore I've encountered some issues when trying to render escaped triple backticks. 42 | 43 | Please do reach out (or send patches) if you know how to solve these. 44 | -------------------------------------------------------------------------------- /examples/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Simple Chat Interface 7 | 8 | 9 | 10 | 11 | 12 |
13 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /examples/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stupid Simple React + Tailwind 7 | 8 | 9 | 10 | 11 | 12 |
Counter
13 |
14 |
TODO List
15 |
16 |
TOGETHER
17 |
18 | 19 | 20 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /examples/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Stupid Simple React + Tailwind 7 | 8 | 9 | 10 | 11 | 12 |
13 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Santa's Todo List 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/todo.html.md: -------------------------------------------------------------------------------- 1 | make me a todo list that stores stuff in the localstorage. Santa clause themee 2 | -------------------------------------------------------------------------------- /experiments/babel.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | JSX Simple React + Tailwind 13 | 14 | 15 | 16 | 17 | 53 | 54 | 55 | 56 |
57 |
we can also use tailwind anywhere
58 |
59 | 60 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /experiments/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dashboard 6 | 7 | 9 | 10 | 11 | 12 |
13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /experiments/simple-react.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple React + Tailwind 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 35 | 36 | 37 | 38 |
39 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /experiments/ssreact-multi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Enhanced Simple React + Tailwind 6 | 7 | 8 | 9 | 10 | 47 | 48 | 49 | 50 | 51 |
52 | 53 |
Some regular HTML without JS/React
54 | 55 |
56 | 57 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Stupid Simple React - React-in-HTML from a 21kb bundle 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 85 | 86 | 87 | 88 |
89 |
94 |
95 |

Stupid Simple React

96 |

Because React doesn't have to be complicated.

97 | 112 |
113 |
114 | 115 |
116 |
117 | 118 |
119 |

Ridiculously Easy to Use

120 |
121 |
// 1. Add the script
122 | <script src="https://ssreact.com/min.js"></script>
123 | 
124 | // 2. Write your component
125 | <script type="text/babel">
126 | 
127 | function Greeting({ name }) {
128 |     const [count, setCount] = React.useState(0);
129 |     return (
130 |         <div onClick={() => setCount(count + 1)}>
131 |             Hello {name}! Clicks: {count}
132 |         </div>
133 |     );
134 | }
135 | </script>
136 | 
137 | // 3. Use it with ssreact.ComponentName
138 | <div id="ssreact.Greeting"></div>
139 |
140 |
141 |
142 | 143 | 157 | 158 | 174 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stupid Simple React 5 | 6 | 7 | 8 | 9 |
10 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t||self).preact={})}(this,(function(t){var e,n,_,i,o,r,s,u,c,l,h,a,f,p="http://www.w3.org/2000/svg",d="http://www.w3.org/1999/xhtml",v=void 0,y={},m=[],k=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,g=Array.isArray;function T(t,e){for(var n in e)t[n]=e[n];return t}function b(t){t&&t.parentNode&&t.parentNode.removeChild(t)}function x(t,n,_){var i,o,r,s={};for(r in n)"key"==r?i=n[r]:"ref"==r?o=n[r]:s[r]=n[r];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):_),"function"==typeof t&&null!=t.defaultProps)for(r in t.defaultProps)s[r]===v&&(s[r]=t.defaultProps[r]);return C(t,s,i,o,null)}function C(t,e,i,o,r){var s={type:t,props:e,key:i,ref:o,__k:null,__:null,__b:0,__e:null,__c:null,constructor:v,__v:null==r?++_:r,__i:-1,__u:0};return null==r&&null!=n.vnode&&n.vnode(s),s}function K(t){return t.children}function E(t,e){this.props=t,this.context=e}function S(t,e){if(null==e)return t.__?S(t.__,t.__i+1):null;for(var n;ee&&o.sort(u));R.__r=0}function P(t,e,n,_,i,o,r,s,u,c,l){var h,a,f,p,d,k,g=_&&_.__k||m,T=e.length;for(u=N(n,e,g,u,T),h=0;h0?C(r.type,r.props,r.key,r.ref?r.ref:null,r.__v):r).__=t,r.__b=t.__b+1,s=null,-1!==(c=r.__i=M(r,n,u,h))&&(h--,(s=n[c])&&(s.__u|=2)),null==s||null===s.__v?(-1==c&&a--,"function"!=typeof r.type&&(r.__u|=4)):c!=u&&(c==u-1?a--:c==u+1?a++:(c>u?a--:a++,r.__u|=4))):t.__k[o]=null;if(h)for(o=0;o(null!=u&&0==(2&u.__u)?1:0))for(i=n-1,o=n+1;i>=0||o=0){if((u=e[i])&&0==(2&u.__u)&&r==u.key&&s===u.type)return i;i--}if(o2&&(u.children=arguments.length>3?e.call(arguments,2):_),C(t.type,u,i||t.key,o||t.ref,null)},t.createContext=function(t,e){var n={__c:e="__cC"+f++,__:t,Consumer:function(t,e){return t.children(e)},Provider:function(t){var n,_;return this.getChildContext||(n=new Set,(_={})[e]=this,this.getChildContext=function(){return _},this.componentWillUnmount=function(){n=null},this.shouldComponentUpdate=function(t){this.props.value!==t.value&&n.forEach((function(t){t.__e=!0,H(t)}))},this.sub=function(t){n.add(t);var e=t.componentWillUnmount;t.componentWillUnmount=function(){n&&n.delete(t),e&&e.call(t)}}),t.children}};return n.Provider.__=n.Consumer.contextType=n},t.createElement=x,t.createRef=function(){return{current:null}},t.h=x,t.hydrate=function t(e,n){q(e,n,t)},t.isValidElement=i,t.options=n,t.render=q,t.toChildArray=function t(e,n){return n=n||[],null==e||"boolean"==typeof e||(g(e)?e.some((function(e){t(e,n)})):n.push(e)),n}})),function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("preact")):"function"==typeof define&&define.amd?define(["exports","preact"],e):e((t||self).preactHooks={},t.preact)}(this,(function(t,e){var n,_,i,o,r=0,s=[],u=e.options,c=u.__b,l=u.__r,h=u.diffed,a=u.__c,f=u.unmount,p=u.__;function d(t,e){u.__h&&u.__h(_,t,r||e),r=0;var n=_.__H||(_.__H={__:[],__h:[]});return t>=n.__.length&&n.__.push({}),n.__[t]}function v(t){return r=1,y(E,t)}function y(t,e,i){var o=d(n++,2);if(o.t=t,!o.__c&&(o.__=[i?i(e):E(void 0,e),function(t){var e=o.__N?o.__N[0]:o.__[0],n=o.t(e,t);e!==n&&(o.__N=[n,o.__[1]],o.__c.setState({}))}],o.__c=_,!_.u)){var r=function(t,e,n){if(!o.__c.__H)return!0;var _=o.__c.__H.__.filter((function(t){return!!t.__c}));if(_.every((function(t){return!t.__N})))return!s||s.call(this,t,e,n);var i=o.__c.props!==t;return _.forEach((function(t){if(t.__N){var e=t.__[0];t.__=t.__N,t.__N=void 0,e!==t.__[0]&&(i=!0)}})),s&&s.call(this,t,e,n)||i};_.u=!0;var s=_.shouldComponentUpdate,u=_.componentWillUpdate;_.componentWillUpdate=function(t,e,n){if(this.__e){var _=s;s=void 0,r(t,e,n),s=_}u&&u.call(this,t,e,n)},_.shouldComponentUpdate=r}return o.__N||o.__}function m(t,e){var i=d(n++,4);!u.__s&&K(i.__H,e)&&(i.__=t,i.i=e,_.__h.push(i))}function k(t,e){var _=d(n++,7);return K(_.__H,e)&&(_.__=t(),_.__H=e,_.__h=t),_.__}function g(){for(var t;t=s.shift();)if(t.__P&&t.__H)try{t.__H.__h.forEach(x),t.__H.__h.forEach(C),t.__H.__h=[]}catch(e){t.__H.__h=[],u.__e(e,t.__v)}}u.__b=function(t){_=null,c&&c(t)},u.__=function(t,e){t&&e.__k&&e.__k.__m&&(t.__m=e.__k.__m),p&&p(t,e)},u.__r=function(t){l&&l(t),n=0;var e=(_=t.__c).__H;e&&(i===_?(e.__h=[],_.__h=[],e.__.forEach((function(t){t.__N&&(t.__=t.__N),t.i=t.__N=void 0}))):(e.__h.forEach(x),e.__h.forEach(C),e.__h=[],n=0)),i=_},u.diffed=function(t){h&&h(t);var e=t.__c;e&&e.__H&&(e.__H.__h.length&&(1!==s.push(e)&&o===u.requestAnimationFrame||((o=u.requestAnimationFrame)||b)(g)),e.__H.__.forEach((function(t){t.i&&(t.__H=t.i),t.i=void 0}))),i=_=null},u.__c=function(t,e){e.some((function(t){try{t.__h.forEach(x),t.__h=t.__h.filter((function(t){return!t.__||C(t)}))}catch(n){e.some((function(t){t.__h&&(t.__h=[])})),e=[],u.__e(n,t.__v)}})),a&&a(t,e)},u.unmount=function(t){f&&f(t);var e,n=t.__c;n&&n.__H&&(n.__H.__.forEach((function(t){try{x(t)}catch(t){e=t}})),n.__H=void 0,e&&u.__e(e,n.__v))};var T="function"==typeof requestAnimationFrame;function b(t){var e,n=function(){clearTimeout(_),T&&cancelAnimationFrame(e),setTimeout(t)},_=setTimeout(n,100);T&&(e=requestAnimationFrame(n))}function x(t){var e=_,n=t.__c;"function"==typeof n&&(t.__c=void 0,n()),_=e}function C(t){var e=_;t.__c=t.__(),_=e}function K(t,e){return!t||t.length!==e.length||e.some((function(e,n){return e!==t[n]}))}function E(t,e){return"function"==typeof e?e(t):e}t.useCallback=function(t,e){return r=8,k((function(){return t}),e)},t.useContext=function(t){var e=_.context[t.__c],i=d(n++,9);return i.c=t,e?(null==i.__&&(i.__=!0,e.sub(_)),e.props.value):t.__},t.useDebugValue=function(t,e){u.useDebugValue&&u.useDebugValue(e?e(t):t)},t.useEffect=function(t,e){var i=d(n++,3);!u.__s&&K(i.__H,e)&&(i.__=t,i.i=e,_.__H.__h.push(i))},t.useErrorBoundary=function(t){var e=d(n++,10),i=v();return e.__=t,_.componentDidCatch||(_.componentDidCatch=function(t,n){e.__&&e.__(t,n),i[1](t)}),[i[0],function(){i[1](void 0)}]},t.useId=function(){var t=d(n++,11);if(!t.__){for(var e=_.__v;null!==e&&!e.__m&&null!==e.__;)e=e.__;var i=e.__m||(e.__m=[0,0]);t.__="P"+i[0]+"-"+i[1]++}return t.__},t.useImperativeHandle=function(t,e,n){r=6,m((function(){return"function"==typeof t?(t(e()),function(){return t(null)}):t?(t.current=e(),function(){return t.current=null}):void 0}),null==n?n:n.concat(t))},t.useLayoutEffect=m,t.useMemo=k,t.useReducer=y,t.useRef=function(t){return r=5,k((function(){return{current:t}}),[])},t.useState=v})),function(t){function e(t,e){this.out="",this.d=t,this.i=0,this.eof=t.length,this.token={type:"TK_START"},this.cache={token:{type:void 0,data:void 0},prev:{type:void 0,data:void 0},i:void 0,oi:void 0},this.prev=void 0,this.write=!0,this.replace=e}function n(t){return" "===t||"\t"===t||"\n"===t}function _(t){return(t=t.charCodeAt(0))>=65&&t<=90||t>=97&&t<=122}function i(t){for(var e="",n=0,_=t.length;n<_;n++)"'"===t[n]&&(e+="\\"),e+=t[n];return e}function o(t){console.log(t),process.exit()}function r(t,n){var _=new e(t,n);return _.run(),_.out}e.prototype.is_valid_char=function(){var t=this.d[this.i];return void 0!==t&&(_(t)||function(t){return(t=t.charCodeAt(0))>=48&&t<=57}(t)||"-"===t||"_"===t||"$"===t)},e.prototype.restore=function(){this.token.type=this.cache.token.type,this.token.data=this.cache.token.data,this.prev.type=this.cache.prev.type,this.prev.data=this.cache.prev.data,this.i=this.cache.i},e.prototype.save=function(){this.cache.token.type=this.token.type,this.cache.token.data=this.token.data,this.cache.prev.type=this.prev.type,this.cache.prev.data=this.prev.data,this.cache.i=this.i},e.prototype.inc=function(){if(this.write){var t=this.i++;return this.out+=this.d[t],t}return this.i++},e.prototype.emit=function(t){this.out+=t},e.prototype.parse_identifier=function(){for(var t="";this.is_valid_char();)t+=this.d[this.inc()];return t},e.prototype.parse_string=function(t){for(var e="";this.i":"TK_GT","=":"TK_EQ","{":"TK_LB","}":"TK_RB",")":"TK_RP","]":"TK_RBR"};for(this.prev=this.token;this.i":case"=":case"{":case"}":case")":case"]":return this.inc(),void(this.token={type:e[i],data:i});case'"':case"'":case"`":if(this.i>=this.eof)break;return this.inc(),void(this.token={type:"TK_STR",data:this.parse_string(i)});case"/":if(this.i>=this.eof)break;var o=t[this.inc()+1];if("/"===o){for(;this.i{document.querySelectorAll('script[type="text/babel"]').forEach((t=>{try{const e=CompileJSX(t.textContent,"React.h"),n=document.createElement("script");n.textContent=e,t.parentNode.replaceChild(n,t)}catch(t){console.error("Error compiling JSX:",t)}})),document.querySelectorAll('[id^="ssreact."]').forEach((e=>{const n=e.id.split(".")[1],_=t[n];"function"==typeof _?React.render(React.h(_),e):console.warn(`Component "${n}" not found for element with id "${e.id}"`)}))}))}("undefined"!=typeof self?self:this); -------------------------------------------------------------------------------- /playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SSReact Playground 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |

SSReact Playground

14 |

Edit the code on the left, see the result on the right!

15 |
16 | 17 |
18 | 19 |
20 |
21 | 25 |
26 | 27 | 28 |
29 |
30 |

Preview

31 |
32 | 33 |
34 |
35 |
36 | 37 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/bundle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JSX Simple React 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/bundle.js: -------------------------------------------------------------------------------- 1 | !function(n,l){"object"==typeof exports&&"undefined"!=typeof module?l(exports):"function"==typeof define&&define.amd?define(["exports"],l):l((n||self).preact={})}(this,function(n){var l,u,t,i,e,r,o,f,c,s,a,h,p,y="http://www.w3.org/2000/svg",v="http://www.w3.org/1999/xhtml",d=void 0,w={},_=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,m=Array.isArray;function b(n,l){for(var u in l)n[u]=l[u];return n}function k(n){n&&n.parentNode&&n.parentNode.removeChild(n)}function x(n,u,t){var i,e,r,o={};for(r in u)"key"==r?i=u[r]:"ref"==r?e=u[r]:o[r]=u[r];if(arguments.length>2&&(o.children=arguments.length>3?l.call(arguments,2):t),"function"==typeof n&&null!=n.defaultProps)for(r in n.defaultProps)o[r]===d&&(o[r]=n.defaultProps[r]);return S(n,o,i,e,null)}function S(n,l,i,e,r){var o={type:n,props:l,key:i,ref:e,__k:null,__:null,__b:0,__e:null,__c:null,constructor:d,__v:null==r?++t:r,__i:-1,__u:0};return null==r&&null!=u.vnode&&u.vnode(o),o}function C(n){return n.children}function M(n,l){this.props=n,this.context=l}function P(n,l){if(null==l)return n.__?P(n.__,n.__i+1):null;for(var u;ll&&e.sort(f));I.__r=0}function A(n,l,u,t,i,e,r,o,f,c,s){var a,h,p,y,v,g,m=t&&t.__k||_,b=l.length;for(f=H(u,l,m,f,b),a=0;a0?S(r.type,r.props,r.key,r.ref?r.ref:null,r.__v):r).__=n,r.__b=n.__b+1,o=null,-1!==(c=r.__i=j(r,u,f,a))&&(a--,(o=u[c])&&(o.__u|=2)),null==o||null===o.__v?(-1==c&&h--,"function"!=typeof r.type&&(r.__u|=4)):c!=f&&(c==f-1?h--:c==f+1?h++:(c>f?h--:h++,r.__u|=4))):n.__k[e]=null;if(a)for(e=0;e(null!=f&&0==(2&f.__u)?1:0))for(i=u-1,e=u+1;i>=0||e=0){if((f=l[i])&&0==(2&f.__u)&&r==f.key&&o===f.type)return i;i--}if(e2&&(f.children=arguments.length>3?l.call(arguments,2):t),S(n.type,f,i||n.key,e||n.ref,null)},n.createContext=function(n,l){var u={__c:l="__cC"+p++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var u,t;return this.getChildContext||(u=new Set,(t={})[l]=this,this.getChildContext=function(){return t},this.componentWillUnmount=function(){u=null},this.shouldComponentUpdate=function(n){this.props.value!==n.value&&u.forEach(function(n){n.__e=!0,$(n)})},this.sub=function(n){u.add(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){u&&u.delete(n),l&&l.call(n)}}),n.children}};return u.Provider.__=u.Consumer.contextType=u},n.createElement=x,n.createRef=function(){return{current:null}},n.h=x,n.hydrate=function n(l,u){G(l,u,n)},n.isValidElement=i,n.options=u,n.render=G,n.toChildArray=function n(l,u){return u=u||[],null==l||"boolean"==typeof l||(m(l)?l.some(function(l){n(l,u)}):u.push(l)),u}}); 2 | //# sourceMappingURL=preact.umd.js.map 3 | 4 | 5 | !function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("preact")):"function"==typeof define&&define.amd?define(["exports","preact"],t):t((n||self).preactHooks={},n.preact)}(this,function(n,t){var u,r,i,o,f=0,c=[],e=t.options,a=e.__b,v=e.__r,l=e.diffed,d=e.__c,s=e.unmount,p=e.__;function h(n,t){e.__h&&e.__h(r,n,f||t),f=0;var u=r.__H||(r.__H={__:[],__h:[]});return n>=u.__.length&&u.__.push({}),u.__[n]}function y(n){return f=1,m(j,n)}function m(n,t,i){var o=h(u++,2);if(o.t=n,!o.__c&&(o.__=[i?i(t):j(void 0,t),function(n){var t=o.__N?o.__N[0]:o.__[0],u=o.t(t,n);t!==u&&(o.__N=[u,o.__[1]],o.__c.setState({}))}],o.__c=r,!r.u)){var f=function(n,t,u){if(!o.__c.__H)return!0;var r=o.__c.__H.__.filter(function(n){return!!n.__c});if(r.every(function(n){return!n.__N}))return!c||c.call(this,n,t,u);var i=o.__c.props!==n;return r.forEach(function(n){if(n.__N){var t=n.__[0];n.__=n.__N,n.__N=void 0,t!==n.__[0]&&(i=!0)}}),c&&c.call(this,n,t,u)||i};r.u=!0;var c=r.shouldComponentUpdate,e=r.componentWillUpdate;r.componentWillUpdate=function(n,t,u){if(this.__e){var r=c;c=void 0,f(n,t,u),c=r}e&&e.call(this,n,t,u)},r.shouldComponentUpdate=f}return o.__N||o.__}function T(n,t){var i=h(u++,4);!e.__s&&g(i.__H,t)&&(i.__=n,i.i=t,r.__h.push(i))}function _(n,t){var r=h(u++,7);return g(r.__H,t)&&(r.__=n(),r.__H=t,r.__h=n),r.__}function b(){for(var n;n=c.shift();)if(n.__P&&n.__H)try{n.__H.__h.forEach(A),n.__H.__h.forEach(F),n.__H.__h=[]}catch(t){n.__H.__h=[],e.__e(t,n.__v)}}e.__b=function(n){r=null,a&&a(n)},e.__=function(n,t){n&&t.__k&&t.__k.__m&&(n.__m=t.__k.__m),p&&p(n,t)},e.__r=function(n){v&&v(n),u=0;var t=(r=n.__c).__H;t&&(i===r?(t.__h=[],r.__h=[],t.__.forEach(function(n){n.__N&&(n.__=n.__N),n.i=n.__N=void 0})):(t.__h.forEach(A),t.__h.forEach(F),t.__h=[],u=0)),i=r},e.diffed=function(n){l&&l(n);var t=n.__c;t&&t.__H&&(t.__H.__h.length&&(1!==c.push(t)&&o===e.requestAnimationFrame||((o=e.requestAnimationFrame)||x)(b)),t.__H.__.forEach(function(n){n.i&&(n.__H=n.i),n.i=void 0})),i=r=null},e.__c=function(n,t){t.some(function(n){try{n.__h.forEach(A),n.__h=n.__h.filter(function(n){return!n.__||F(n)})}catch(u){t.some(function(n){n.__h&&(n.__h=[])}),t=[],e.__e(u,n.__v)}}),d&&d(n,t)},e.unmount=function(n){s&&s(n);var t,u=n.__c;u&&u.__H&&(u.__H.__.forEach(function(n){try{A(n)}catch(n){t=n}}),u.__H=void 0,t&&e.__e(t,u.__v))};var q="function"==typeof requestAnimationFrame;function x(n){var t,u=function(){clearTimeout(r),q&&cancelAnimationFrame(t),setTimeout(n)},r=setTimeout(u,100);q&&(t=requestAnimationFrame(u))}function A(n){var t=r,u=n.__c;"function"==typeof u&&(n.__c=void 0,u()),r=t}function F(n){var t=r;n.__c=n.__(),r=t}function g(n,t){return!n||n.length!==t.length||t.some(function(t,u){return t!==n[u]})}function j(n,t){return"function"==typeof t?t(n):t}n.useCallback=function(n,t){return f=8,_(function(){return n},t)},n.useContext=function(n){var t=r.context[n.__c],i=h(u++,9);return i.c=n,t?(null==i.__&&(i.__=!0,t.sub(r)),t.props.value):n.__},n.useDebugValue=function(n,t){e.useDebugValue&&e.useDebugValue(t?t(n):n)},n.useEffect=function(n,t){var i=h(u++,3);!e.__s&&g(i.__H,t)&&(i.__=n,i.i=t,r.__H.__h.push(i))},n.useErrorBoundary=function(n){var t=h(u++,10),i=y();return t.__=n,r.componentDidCatch||(r.componentDidCatch=function(n,u){t.__&&t.__(n,u),i[1](n)}),[i[0],function(){i[1](void 0)}]},n.useId=function(){var n=h(u++,11);if(!n.__){for(var t=r.__v;null!==t&&!t.__m&&null!==t.__;)t=t.__;var i=t.__m||(t.__m=[0,0]);n.__="P"+i[0]+"-"+i[1]++}return n.__},n.useImperativeHandle=function(n,t,u){f=6,T(function(){return"function"==typeof n?(n(t()),function(){return n(null)}):n?(n.current=t(),function(){return n.current=null}):void 0},null==u?u:u.concat(n))},n.useLayoutEffect=T,n.useMemo=_,n.useReducer=m,n.useRef=function(n){return f=5,_(function(){return{current:n}},[])},n.useState=y}); 6 | //# sourceMappingURL=hooks.umd.js.map 7 | 8 | 9 | /* 10 | * A tiny JSX compiler 11 | * Author: Matthew Levenstein 12 | * License: MIT 13 | */ 14 | 15 | (function (window) { 16 | function Compiler(code, replace) { 17 | this.out = ""; 18 | this.d = code; 19 | this.i = 0; 20 | this.eof = code.length; 21 | this.token = { 22 | type: "TK_START", 23 | }; 24 | this.cache = { 25 | token: { type: undefined, data: undefined }, 26 | prev: { type: undefined, data: undefined }, 27 | i: undefined, 28 | oi: undefined, 29 | }; 30 | this.prev = undefined; 31 | this.write = true; 32 | this.replace = replace; 33 | } 34 | 35 | function is_space(c) { 36 | return c === " " || c === "\t" || c === "\n"; 37 | } 38 | 39 | function is_numeric(c) { 40 | c = c.charCodeAt(0); 41 | return c >= 48 && c <= 57; 42 | } 43 | 44 | function is_alpha(c) { 45 | c = c.charCodeAt(0); 46 | return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); 47 | } 48 | 49 | function escape(str) { 50 | var out = ""; 51 | for (var i = 0, l = str.length; i < l; i++) { 52 | if (str[i] === "'") out += "\\"; 53 | out += str[i]; 54 | } 55 | return out; 56 | } 57 | 58 | function prepare(str) { 59 | // NB: removed trim here to prevent spaces from not showing up before variable names 60 | return escape(str.replace(/ /g, " ")); 61 | } 62 | 63 | function error(e) { 64 | console.log(e); 65 | process.exit(); 66 | } 67 | 68 | Compiler.prototype.is_valid_char = function () { 69 | var c = this.d[this.i]; 70 | return ( 71 | c !== undefined && 72 | (is_alpha(c) || is_numeric(c) || c === "-" || c === "_" || c === "$") 73 | ); 74 | }; 75 | 76 | Compiler.prototype.restore = function () { 77 | this.token.type = this.cache.token.type; 78 | this.token.data = this.cache.token.data; 79 | this.prev.type = this.cache.prev.type; 80 | this.prev.data = this.cache.prev.data; 81 | this.i = this.cache.i; 82 | }; 83 | 84 | Compiler.prototype.save = function () { 85 | this.cache.token.type = this.token.type; 86 | this.cache.token.data = this.token.data; 87 | this.cache.prev.type = this.prev.type; 88 | this.cache.prev.data = this.prev.data; 89 | this.cache.i = this.i; 90 | }; 91 | 92 | Compiler.prototype.inc = function () { 93 | if (this.write) { 94 | var i = this.i++; 95 | this.out += this.d[i]; 96 | return i; 97 | } else { 98 | return this.i++; 99 | } 100 | }; 101 | 102 | Compiler.prototype.emit = function (code) { 103 | this.out += code; 104 | }; 105 | 106 | Compiler.prototype.parse_identifier = function () { 107 | var id = ""; 108 | while (this.is_valid_char()) { 109 | id += this.d[this.inc()]; 110 | } 111 | return id; 112 | }; 113 | 114 | Compiler.prototype.parse_string = function (del) { 115 | var str = ""; 116 | while (this.i < this.eof) { 117 | if ( 118 | this.d[this.i] !== del || 119 | (this.d[this.i] === del && this.d[this.i - 1] === "\\") 120 | ) 121 | str += this.d[this.inc()]; 122 | else break; 123 | } 124 | this.inc(); 125 | return str; 126 | }; 127 | 128 | Compiler.prototype.next = function () { 129 | var _d = this.d; 130 | var tokens = { 131 | "<": "TK_LT", 132 | ">": "TK_GT", 133 | "=": "TK_EQ", 134 | "{": "TK_LB", 135 | "}": "TK_RB", 136 | ")": "TK_RP", 137 | "]": "TK_RBR", 138 | }; 139 | this.prev = this.token; 140 | while (this.i < this.eof) { 141 | var c = _d[this.i]; 142 | switch (c) { 143 | case "<": 144 | case ">": 145 | case "=": 146 | case "{": 147 | case "}": 148 | case ")": 149 | case "]": 150 | { 151 | this.inc(); 152 | this.token = { 153 | type: tokens[c], 154 | data: c, 155 | }; 156 | } 157 | return; 158 | case '"': 159 | case "'": 160 | case "`": 161 | { 162 | if (this.i >= this.eof) { 163 | break; 164 | } 165 | this.inc(); 166 | this.token = { 167 | type: "TK_STR", 168 | data: this.parse_string(c), 169 | }; 170 | } 171 | return; 172 | case "/": 173 | { 174 | if (this.i >= this.eof) { 175 | break; 176 | } 177 | var c1 = _d[this.inc() + 1]; 178 | if (c1 === "/") { 179 | while (this.i < this.eof && _d[this.i] !== "\n") { 180 | this.inc(); 181 | } 182 | this.inc(); 183 | this.token = { 184 | type: "TK_COM", 185 | }; 186 | } else if (c1 === "*") { 187 | while (this.i < this.eof - 1) { 188 | if (_d[this.i] === "*" && _d[this.i + 1] === "/") break; 189 | } 190 | this.inc(); 191 | this.token = { 192 | type: "TK_COM", 193 | }; 194 | } else { 195 | this.token = { 196 | type: "TK_FS", 197 | }; 198 | } 199 | } 200 | return; 201 | default: { 202 | if (is_alpha(c) || c === "-" || c === "_" || c === "$") { 203 | this.token = { 204 | type: "TK_NM", 205 | data: this.parse_identifier(), 206 | }; 207 | return; 208 | } else if (is_space(c)) { 209 | this.inc(); 210 | } else { 211 | this.inc(); 212 | this.token = { 213 | data: c, 214 | type: "TK_SYS", 215 | }; 216 | return; 217 | } 218 | } 219 | } 220 | } 221 | this.token = { 222 | type: "TK_EOF", 223 | }; 224 | }; 225 | 226 | Compiler.prototype.accept = function (t) { 227 | if (this.token.type === t) { 228 | this.next(); 229 | return true; 230 | } else { 231 | return false; 232 | } 233 | }; 234 | 235 | Compiler.prototype.expect = function (t) { 236 | if (this.token.type === t) { 237 | this.next(); 238 | } else { 239 | error("Unexpected token: " + this.token.type + ", Expected: " + t); 240 | } 241 | }; 242 | 243 | Compiler.prototype.tag_body = function (name) { 244 | this.emit(", "); 245 | if (this.token.type === "TK_LB") { 246 | this.emit("("); 247 | this.write = true; 248 | } 249 | if (this.accept("TK_LB")) { 250 | this.jsexpr(); 251 | this.emit(")"); 252 | this.tag_body(name); 253 | } else if (this.accept("TK_LT")) { 254 | this.save(); 255 | if (!this.accept("TK_FS")) { 256 | this.parse_tag(true); 257 | this.tag_body(name); 258 | } else { 259 | // the tag body is over 260 | this.restore(); 261 | this.out = this.out.slice(0, -2); 262 | } 263 | } else { 264 | var inner = this.token.data; 265 | while ( 266 | this.d[this.i] !== "{" && 267 | this.d[this.i] !== "<" && 268 | this.i < this.eof 269 | ) { 270 | if (this.d[this.i] !== "\n") inner += this.d[this.inc()]; 271 | else this.inc(); 272 | } 273 | this.next(); 274 | this.emit("'" + prepare(inner) + "'"); 275 | if (this.i < this.eof - 1) this.tag_body(name); 276 | } 277 | }; 278 | 279 | Compiler.prototype.tag_close = function (name) { 280 | if (this.accept("TK_FS")) { 281 | this.expect("TK_GT"); 282 | } else { 283 | this.expect("TK_GT"); 284 | this.tag_body(name); 285 | this.expect("TK_FS"); 286 | this.expect("TK_NM"); 287 | if (this.prev.data !== name) { 288 | error("Expected closing tag for " + name); 289 | } 290 | this.expect("TK_GT"); 291 | } 292 | }; 293 | 294 | Compiler.prototype.jsexpr = function () { 295 | if (this.node_list(true) === false) { 296 | error("Expected }"); 297 | } else { 298 | this.expect("TK_RB"); 299 | } 300 | }; 301 | 302 | Compiler.prototype.params = function (first) { 303 | if (this.token.type === "TK_FS" || this.token.type === "TK_GT") { 304 | if (!first) this.emit("}"); 305 | else this.emit("null"); 306 | return; 307 | } 308 | if (first) { 309 | this.emit("{"); 310 | } else { 311 | this.emit(", "); 312 | } 313 | if (this.accept("TK_NM")) { 314 | var k = this.prev.data; 315 | var v = undefined; 316 | this.emit("'" + k + "': "); 317 | if (this.accept("TK_EQ")) { 318 | if (this.token.type === "TK_LB") { 319 | this.emit("("); 320 | this.write = true; 321 | } 322 | if (this.accept("TK_STR")) { 323 | v = "'" + escape(this.prev.data) + "'"; 324 | } else if (this.accept("TK_LB")) { 325 | this.jsexpr(); 326 | this.emit(")"); 327 | } else { 328 | error("Unexpected token: " + this.token.data || this.token.type); 329 | } 330 | } else { 331 | v = true; 332 | } 333 | if (v) this.emit(v); 334 | this.params(); 335 | } 336 | }; 337 | 338 | function isUpper(c) { 339 | return c === c.toUpperCase(); 340 | } 341 | 342 | Compiler.prototype.parse_tag = function (inBody) { 343 | this.expect("TK_NM"); 344 | var name = this.prev.data; 345 | this.emit( 346 | this.replace + "(" + (isUpper(name[0]) ? name : "'" + name + "'") + ", ", 347 | ); 348 | this.params(true); 349 | this.tag_close(name); 350 | this.emit(")"); 351 | // don't do this if it's part of innertext 352 | if (inBody) { 353 | return; 354 | } 355 | if (this.token.data && this.token.data !== "<" && this.token.data !== "{") { 356 | if (this.token.data === "return") { 357 | this.emit("\n"); 358 | } 359 | this.emit(this.token.data); 360 | } 361 | }; 362 | 363 | Compiler.prototype.possible_tag = function () { 364 | if (this.token.type === "TK_LT" && this.prev !== undefined) { 365 | if ( 366 | !(this.prev.type === "TK_NM" && this.prev.data !== "return") && 367 | !(this.prev.type === "TK_RP") && 368 | !(this.prev.type === "TK_RBR") 369 | ) { 370 | this.out = this.out.slice(0, -1); // remove trailing < 371 | this.write = false; 372 | this.next(); 373 | this.parse_tag(); 374 | this.write = true; 375 | } else { 376 | this.next(); 377 | } 378 | } else { 379 | this.next(); 380 | } 381 | }; 382 | 383 | Compiler.prototype.node_list = function (expr) { 384 | // if called from within a javascript expression 385 | if (expr) { 386 | if (this.token.type === "TK_RB") { 387 | this.out = this.out.slice(0, -1); // remove trailing } 388 | this.write = false; 389 | this.emit("undefined"); 390 | return true; 391 | } 392 | var count = -1; 393 | while (this.token.type !== "TK_EOF") { 394 | if (this.token.type === "TK_LB") count--; 395 | if (this.token.type === "TK_RB") count++; 396 | if (count === 0) { 397 | this.out = this.out.slice(0, -1); // remove trailing } 398 | this.write = false; 399 | return true; 400 | } 401 | this.possible_tag(); 402 | } 403 | return false; 404 | } 405 | while (this.token.type !== "TK_EOF") { 406 | this.possible_tag(); 407 | } 408 | return true; 409 | }; 410 | 411 | Compiler.prototype.root = function (expr) { 412 | this.node_list(false); 413 | }; 414 | 415 | Compiler.prototype.run = function () { 416 | this.next(); 417 | this.root(); 418 | }; 419 | 420 | function _compile(code, replace) { 421 | var compiler = new Compiler(code, replace); 422 | compiler.run(); 423 | return compiler.out; 424 | } 425 | 426 | if ("undefined" === typeof module) { 427 | window.CompileJSX = _compile; 428 | } else { 429 | module.exports = _compile; 430 | } 431 | })(this); 432 | 433 | 434 | (function (global) { 435 | // Set up React global 436 | global.React = { 437 | ...preact, 438 | useState: preactHooks.useState, 439 | useEffect: preactHooks.useEffect, 440 | useRef: preactHooks.useRef, 441 | useCallback: preactHooks.useCallback, 442 | useMemo: preactHooks.useMemo, 443 | }; 444 | 445 | // Process JSX scripts 446 | function processScripts() { 447 | const scripts = document.querySelectorAll('script[type="text/babel"]'); 448 | scripts.forEach((script) => { 449 | try { 450 | const compiledCode = CompileJSX(script.textContent, "React.h"); 451 | const newScript = document.createElement("script"); 452 | newScript.textContent = compiledCode; 453 | script.parentNode.replaceChild(newScript, script); 454 | } catch (error) { 455 | console.error("Error compiling JSX:", error); 456 | } 457 | }); 458 | } 459 | 460 | // Mount components 461 | function mountComponents() { 462 | const components = document.querySelectorAll('[id^="ssreact."]'); 463 | components.forEach((element) => { 464 | const componentName = element.id.split(".")[1]; 465 | const Component = global[componentName]; 466 | if (typeof Component === "function") { 467 | React.render(React.h(Component), element); 468 | } else { 469 | console.warn( 470 | `Component "${componentName}" not found for element with id "${element.id}"`, 471 | ); 472 | } 473 | }); 474 | } 475 | 476 | // Initialize when DOM is ready 477 | document.addEventListener("DOMContentLoaded", () => { 478 | processScripts(); 479 | mountComponents(); 480 | }); 481 | })(typeof self !== "undefined" ? self : this); 482 | -------------------------------------------------------------------------------- /src/generate-all.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | 3 | const preactVersion = "10.25.4"; 4 | const urls = [ 5 | `https://unpkg.com/preact@${preactVersion}/dist/preact.umd.js`, 6 | `https://cdn.jsdelivr.net/npm/preact@${preactVersion}/hooks/dist/hooks.umd.js`, 7 | ]; 8 | const files = ["./microjsx.js", "./ssreact.js"]; 9 | 10 | const code = files.map((path) => fs.readFileSync(path, "utf8")); 11 | const urlCode = await Promise.all( 12 | urls.map((url) => fetch(url).then((res) => res.text())), 13 | ); 14 | const allCode = urlCode.concat(code).join("\n\n"); 15 | fs.writeFileSync("./bundle.js", allCode, "utf8"); 16 | // import { Language, minify } from "https://deno.land/x/minifier/mod.ts"; 17 | // console.log(minify(Language.JS, allCode)); 18 | -------------------------------------------------------------------------------- /src/microjsx.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A tiny JSX compiler 3 | * Author: Matthew Levenstein 4 | * License: MIT 5 | */ 6 | 7 | (function (window) { 8 | function Compiler(code, replace) { 9 | this.out = ""; 10 | this.d = code; 11 | this.i = 0; 12 | this.eof = code.length; 13 | this.token = { 14 | type: "TK_START", 15 | }; 16 | this.cache = { 17 | token: { type: undefined, data: undefined }, 18 | prev: { type: undefined, data: undefined }, 19 | i: undefined, 20 | oi: undefined, 21 | }; 22 | this.prev = undefined; 23 | this.write = true; 24 | this.replace = replace; 25 | } 26 | 27 | function is_space(c) { 28 | return c === " " || c === "\t" || c === "\n"; 29 | } 30 | 31 | function is_numeric(c) { 32 | c = c.charCodeAt(0); 33 | return c >= 48 && c <= 57; 34 | } 35 | 36 | function is_alpha(c) { 37 | c = c.charCodeAt(0); 38 | return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); 39 | } 40 | 41 | function escape(str) { 42 | var out = ""; 43 | for (var i = 0, l = str.length; i < l; i++) { 44 | if (str[i] === "'") out += "\\"; 45 | out += str[i]; 46 | } 47 | return out; 48 | } 49 | 50 | function prepare(str) { 51 | // NB: removed trim here to prevent spaces from not showing up before variable names 52 | return escape(str.replace(/ /g, " ")); 53 | } 54 | 55 | function error(e) { 56 | console.log(e); 57 | process.exit(); 58 | } 59 | 60 | Compiler.prototype.is_valid_char = function () { 61 | var c = this.d[this.i]; 62 | 63 | return ( 64 | c !== undefined && 65 | (is_alpha(c) || is_numeric(c) || c === "-" || c === "_" || c === "$") 66 | ); 67 | }; 68 | 69 | Compiler.prototype.restore = function () { 70 | this.token.type = this.cache.token.type; 71 | this.token.data = this.cache.token.data; 72 | this.prev.type = this.cache.prev.type; 73 | this.prev.data = this.cache.prev.data; 74 | this.i = this.cache.i; 75 | }; 76 | 77 | Compiler.prototype.save = function () { 78 | this.cache.token.type = this.token.type; 79 | this.cache.token.data = this.token.data; 80 | this.cache.prev.type = this.prev.type; 81 | this.cache.prev.data = this.prev.data; 82 | this.cache.i = this.i; 83 | }; 84 | 85 | Compiler.prototype.inc = function () { 86 | if (this.write) { 87 | var i = this.i++; 88 | this.out += this.d[i]; 89 | return i; 90 | } else { 91 | return this.i++; 92 | } 93 | }; 94 | 95 | Compiler.prototype.emit = function (code) { 96 | this.out += code; 97 | }; 98 | 99 | Compiler.prototype.parse_identifier = function () { 100 | var id = ""; 101 | while (this.is_valid_char()) { 102 | id += this.d[this.inc()]; 103 | } 104 | return id; 105 | }; 106 | 107 | Compiler.prototype.parse_string = function (del) { 108 | var str = ""; 109 | while (this.i < this.eof) { 110 | if ( 111 | this.d[this.i] !== del || 112 | (this.d[this.i] === del && this.d[this.i - 1] === "\\") 113 | ) 114 | str += this.d[this.inc()]; 115 | else break; 116 | } 117 | this.inc(); 118 | return str; 119 | }; 120 | 121 | Compiler.prototype.next = function () { 122 | var _d = this.d; 123 | var tokens = { 124 | "<": "TK_LT", 125 | ">": "TK_GT", 126 | "=": "TK_EQ", 127 | "{": "TK_LB", 128 | "}": "TK_RB", 129 | ")": "TK_RP", 130 | "]": "TK_RBR", 131 | }; 132 | this.prev = this.token; 133 | while (this.i < this.eof) { 134 | var c = _d[this.i]; 135 | switch (c) { 136 | case "<": 137 | case ">": 138 | case "=": 139 | case "{": 140 | case "}": 141 | case ")": 142 | case "]": 143 | { 144 | this.inc(); 145 | this.token = { 146 | type: tokens[c], 147 | data: c, 148 | }; 149 | } 150 | return; 151 | case '"': 152 | case "'": 153 | case "`": 154 | { 155 | if (this.i >= this.eof) { 156 | break; 157 | } 158 | this.inc(); 159 | this.token = { 160 | type: "TK_STR", 161 | data: this.parse_string(c), 162 | }; 163 | } 164 | return; 165 | case "/": 166 | { 167 | if (this.i >= this.eof) { 168 | break; 169 | } 170 | var c1 = _d[this.inc() + 1]; 171 | if (c1 === "/") { 172 | while (this.i < this.eof && _d[this.i] !== "\n") { 173 | this.inc(); 174 | } 175 | this.inc(); 176 | this.token = { 177 | type: "TK_COM", 178 | }; 179 | } else if (c1 === "*") { 180 | while (this.i < this.eof - 1) { 181 | if (_d[this.i] === "*" && _d[this.i + 1] === "/") break; 182 | } 183 | this.inc(); 184 | this.token = { 185 | type: "TK_COM", 186 | }; 187 | } else { 188 | this.token = { 189 | type: "TK_FS", 190 | }; 191 | } 192 | } 193 | return; 194 | default: { 195 | if (is_alpha(c) || c === "-" || c === "_" || c === "$") { 196 | this.token = { 197 | type: "TK_NM", 198 | data: this.parse_identifier(), 199 | }; 200 | return; 201 | } else if (is_space(c)) { 202 | this.inc(); 203 | } else { 204 | this.inc(); 205 | this.token = { 206 | data: c, 207 | type: "TK_SYS", 208 | }; 209 | return; 210 | } 211 | } 212 | } 213 | } 214 | this.token = { 215 | type: "TK_EOF", 216 | }; 217 | }; 218 | 219 | Compiler.prototype.accept = function (t) { 220 | if (this.token.type === t) { 221 | this.next(); 222 | return true; 223 | } else { 224 | return false; 225 | } 226 | }; 227 | 228 | Compiler.prototype.expect = function (t) { 229 | if (this.token.type === t) { 230 | this.next(); 231 | } else { 232 | error("Unexpected token: " + this.token.type + ", Expected: " + t); 233 | } 234 | }; 235 | 236 | Compiler.prototype.tag_body = function (name) { 237 | this.emit(", "); 238 | if (this.token.type === "TK_LB") { 239 | this.emit("("); 240 | this.write = true; 241 | } 242 | if (this.accept("TK_LB")) { 243 | this.jsexpr(); 244 | this.emit(")"); 245 | this.tag_body(name); 246 | } else if (this.accept("TK_LT")) { 247 | this.save(); 248 | if (!this.accept("TK_FS")) { 249 | this.parse_tag(true); 250 | this.tag_body(name); 251 | } else { 252 | // the tag body is over 253 | this.restore(); 254 | this.out = this.out.slice(0, -2); 255 | } 256 | } else { 257 | var inner = this.token.data; 258 | while ( 259 | this.d[this.i] !== "{" && 260 | this.d[this.i] !== "<" && 261 | this.i < this.eof 262 | ) { 263 | if (this.d[this.i] !== "\n") inner += this.d[this.inc()]; 264 | else this.inc(); 265 | } 266 | this.next(); 267 | this.emit("'" + prepare(inner) + "'"); 268 | if (this.i < this.eof - 1) this.tag_body(name); 269 | } 270 | }; 271 | 272 | Compiler.prototype.tag_close = function (name) { 273 | if (this.accept("TK_FS")) { 274 | this.expect("TK_GT"); 275 | } else { 276 | this.expect("TK_GT"); 277 | this.tag_body(name); 278 | this.expect("TK_FS"); 279 | this.expect("TK_NM"); 280 | if (this.prev.data !== name) { 281 | error("Expected closing tag for " + name); 282 | } 283 | this.expect("TK_GT"); 284 | } 285 | }; 286 | 287 | Compiler.prototype.jsexpr = function () { 288 | if (this.node_list(true) === false) { 289 | error("Expected }"); 290 | } else { 291 | this.expect("TK_RB"); 292 | } 293 | }; 294 | 295 | Compiler.prototype.params = function (first) { 296 | if (this.token.type === "TK_FS" || this.token.type === "TK_GT") { 297 | if (!first) this.emit("}"); 298 | else this.emit("null"); 299 | return; 300 | } 301 | if (first) { 302 | this.emit("{"); 303 | } else { 304 | this.emit(", "); 305 | } 306 | if (this.accept("TK_NM")) { 307 | var k = this.prev.data; 308 | var v = undefined; 309 | this.emit("'" + k + "': "); 310 | if (this.accept("TK_EQ")) { 311 | if (this.token.type === "TK_LB") { 312 | this.emit("("); 313 | this.write = true; 314 | } 315 | if (this.accept("TK_STR")) { 316 | v = "'" + escape(this.prev.data) + "'"; 317 | } else if (this.accept("TK_LB")) { 318 | this.jsexpr(); 319 | this.emit(")"); 320 | } else { 321 | error("Unexpected token: " + this.token.data || this.token.type); 322 | } 323 | } else { 324 | v = true; 325 | } 326 | if (v) this.emit(v); 327 | this.params(); 328 | } 329 | }; 330 | 331 | function isUpper(c) { 332 | return c === c.toUpperCase(); 333 | } 334 | 335 | Compiler.prototype.parse_tag = function (inBody) { 336 | this.expect("TK_NM"); 337 | var name = this.prev.data; 338 | this.emit( 339 | this.replace + "(" + (isUpper(name[0]) ? name : "'" + name + "'") + ", ", 340 | ); 341 | this.params(true); 342 | this.tag_close(name); 343 | this.emit(")"); 344 | // don't do this if it's part of innertext 345 | if (inBody) { 346 | return; 347 | } 348 | if (this.token.data && this.token.data !== "<" && this.token.data !== "{") { 349 | if (this.token.data === "return") { 350 | this.emit("\n"); 351 | } 352 | this.emit(this.token.data); 353 | } 354 | }; 355 | 356 | Compiler.prototype.possible_tag = function () { 357 | if (this.token.type === "TK_LT" && this.prev !== undefined) { 358 | if ( 359 | !(this.prev.type === "TK_NM" && this.prev.data !== "return") && 360 | !(this.prev.type === "TK_RP") && 361 | !(this.prev.type === "TK_RBR") 362 | ) { 363 | this.out = this.out.slice(0, -1); // remove trailing < 364 | this.write = false; 365 | this.next(); 366 | this.parse_tag(); 367 | this.write = true; 368 | } else { 369 | this.next(); 370 | } 371 | } else { 372 | this.next(); 373 | } 374 | }; 375 | 376 | Compiler.prototype.node_list = function (expr) { 377 | // if called from within a javascript expression 378 | if (expr) { 379 | if (this.token.type === "TK_RB") { 380 | this.out = this.out.slice(0, -1); // remove trailing } 381 | this.write = false; 382 | this.emit("undefined"); 383 | return true; 384 | } 385 | var count = -1; 386 | while (this.token.type !== "TK_EOF") { 387 | if (this.token.type === "TK_LB") count--; 388 | if (this.token.type === "TK_RB") count++; 389 | if (count === 0) { 390 | this.out = this.out.slice(0, -1); // remove trailing } 391 | this.write = false; 392 | return true; 393 | } 394 | this.possible_tag(); 395 | } 396 | return false; 397 | } 398 | while (this.token.type !== "TK_EOF") { 399 | this.possible_tag(); 400 | } 401 | return true; 402 | }; 403 | 404 | Compiler.prototype.root = function (expr) { 405 | this.node_list(false); 406 | }; 407 | 408 | Compiler.prototype.run = function () { 409 | this.next(); 410 | this.root(); 411 | }; 412 | 413 | function _compile(code, replace) { 414 | var compiler = new Compiler(code, replace); 415 | compiler.run(); 416 | return compiler.out; 417 | } 418 | 419 | if ("undefined" === typeof module) { 420 | window.compileJSX = _compile; 421 | } else { 422 | module.exports = _compile; 423 | } 424 | })(this); 425 | -------------------------------------------------------------------------------- /src/microjsx2.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | function JSXCompiler(code, replaceFunc = "React.createElement") { 3 | if (!(this instanceof JSXCompiler)) { 4 | return new JSXCompiler(code, replaceFunc); 5 | } 6 | 7 | this.code = code; 8 | this.replaceFunc = replaceFunc; 9 | this.pos = 0; 10 | this.output = ""; 11 | this.length = code.length; 12 | } 13 | 14 | JSXCompiler.prototype = { 15 | isWhitespace(char) { 16 | return /\s/.test(char); 17 | }, 18 | 19 | isValidIdentifierChar(char) { 20 | return /[$\w-]/.test(char); 21 | }, 22 | 23 | peek(offset = 0) { 24 | return this.code[this.pos + offset]; 25 | }, 26 | 27 | advance() { 28 | if (this.pos < this.length) { 29 | this.output += this.code[this.pos]; 30 | } 31 | return this.code[this.pos++]; 32 | }, 33 | 34 | // New method to properly handle UTF-16 surrogate pairs for emojis 35 | getNextCodePoint() { 36 | const first = this.code.charCodeAt(this.pos); 37 | // Check if this is a high surrogate 38 | if (first >= 0xd800 && first <= 0xdbff && this.pos + 1 < this.length) { 39 | const second = this.code.charCodeAt(this.pos + 1); 40 | // Check if next char is a low surrogate 41 | if (second >= 0xdc00 && second <= 0xdfff) { 42 | // Advance twice for surrogate pair 43 | this.pos += 2; 44 | // Combine surrogate pair 45 | return String.fromCodePoint( 46 | ((first - 0xd800) << 10) + (second - 0xdc00) + 0x10000, 47 | ); 48 | } 49 | } 50 | // Single character 51 | this.pos++; 52 | return String.fromCodePoint(first); 53 | }, 54 | 55 | encodeString(str) { 56 | let result = ""; 57 | let i = 0; 58 | while (i < str.length) { 59 | const char = str[i]; 60 | if (char === '"' || char === "\\") { 61 | result += "\\" + char; 62 | i++; 63 | continue; 64 | } 65 | 66 | // Handle surrogate pairs for emojis 67 | const code = str.codePointAt(i); 68 | if (code > 0xffff) { 69 | result += str.slice(i, i + 2); 70 | i += 2; 71 | } else { 72 | result += char; 73 | i++; 74 | } 75 | } 76 | return result; 77 | }, 78 | 79 | skipWhitespace() { 80 | while (this.pos < this.length && this.isWhitespace(this.peek())) { 81 | this.advance(); 82 | } 83 | }, 84 | 85 | parseIdentifier() { 86 | let identifier = ""; 87 | while ( 88 | this.pos < this.length && 89 | this.isValidIdentifierChar(this.peek()) 90 | ) { 91 | identifier += this.advance(); 92 | } 93 | return identifier; 94 | }, 95 | 96 | parseString(delimiter) { 97 | let str = ""; 98 | this.advance(); // Skip opening quote 99 | while (this.pos < this.length) { 100 | if (this.peek() === delimiter && this.peek(-1) !== "\\") break; 101 | 102 | // Handle escape sequences 103 | if (this.peek() === "\\") { 104 | str += this.advance(); // Keep the backslash 105 | if (this.pos < this.length) { 106 | str += this.advance(); // Keep the escaped character 107 | } 108 | continue; 109 | } 110 | 111 | // Handle potential emoji or other Unicode characters 112 | const code = this.code.codePointAt(this.pos); 113 | if (code > 0xffff) { 114 | // This is a surrogate pair (emoji or other special character) 115 | str += this.code.slice(this.pos, this.pos + 2); 116 | this.pos += 2; 117 | } else { 118 | str += this.advance(); 119 | } 120 | } 121 | this.advance(); // Skip closing quote 122 | return str; 123 | }, 124 | 125 | parseJSExpression() { 126 | let depth = 1; 127 | let expr = ""; 128 | while (this.pos < this.length && depth > 0) { 129 | const char = this.peek(); 130 | if (char === "{") depth++; 131 | if (char === "}") depth--; 132 | if (depth > 0) { 133 | // Handle potential emoji in expressions 134 | const code = this.code.codePointAt(this.pos); 135 | if (code > 0xffff) { 136 | expr += this.code.slice(this.pos, this.pos + 2); 137 | this.pos += 2; 138 | } else { 139 | expr += this.advance(); 140 | } 141 | } 142 | } 143 | return expr; 144 | }, 145 | 146 | parseTagAttributes() { 147 | const attrs = []; 148 | 149 | while (this.pos < this.length) { 150 | this.skipWhitespace(); 151 | if (this.peek() === ">" || this.peek() === "/" || this.peek() === "{") 152 | break; 153 | 154 | const attrName = this.parseIdentifier(); 155 | if (!attrName) break; 156 | 157 | let attrValue; 158 | 159 | this.skipWhitespace(); 160 | if (this.peek() === "=") { 161 | this.advance(); // Skip = 162 | this.skipWhitespace(); 163 | 164 | if (this.peek() === "{") { 165 | this.advance(); // Skip opening { 166 | attrValue = this.parseJSExpression(); 167 | this.advance(); // Skip closing } 168 | attrs.push(`"${attrName}": ${attrValue}`); 169 | } else if (this.peek() === '"' || this.peek() === "'") { 170 | const quote = this.peek(); 171 | attrValue = this.parseString(quote); 172 | attrs.push(`"${attrName}": "${this.encodeString(attrValue)}"`); 173 | } 174 | } else { 175 | attrs.push(`"${attrName}": true`); 176 | } 177 | } 178 | 179 | return `{${attrs.join(", ")}}`; 180 | }, 181 | 182 | parseTag() { 183 | this.advance(); // Skip < 184 | const tagName = this.parseIdentifier(); 185 | const isComponent = tagName[0] === tagName[0].toUpperCase(); 186 | 187 | let output = `${this.replaceFunc}(${ 188 | isComponent ? tagName : `"${tagName}"` 189 | }`; 190 | 191 | this.skipWhitespace(); 192 | const attrs = this.parseTagAttributes(); 193 | output += `, ${attrs}`; 194 | 195 | if (this.peek() === "/") { 196 | this.advance(); 197 | this.advance(); // Skip /> 198 | output += `, null)`; 199 | } else { 200 | this.advance(); // Skip > 201 | const children = []; 202 | 203 | while (this.pos < this.length) { 204 | if (this.peek() === "<" && this.peek(1) === "/") { 205 | break; 206 | } 207 | 208 | if (this.peek() === "{") { 209 | this.advance(); // Skip { 210 | const expr = this.parseJSExpression(); 211 | this.advance(); // Skip } 212 | if (expr.trim()) { 213 | children.push(expr); 214 | } 215 | } else if (this.peek() === "<") { 216 | children.push(this.parseTag()); 217 | } else { 218 | let text = ""; 219 | while ( 220 | this.pos < this.length && 221 | this.peek() !== "<" && 222 | this.peek() !== "{" 223 | ) { 224 | // Handle potential emoji in text content 225 | const code = this.code.codePointAt(this.pos); 226 | if (code > 0xffff) { 227 | text += this.code.slice(this.pos, this.pos + 2); 228 | this.pos += 2; 229 | } else { 230 | text += this.advance(); 231 | } 232 | } 233 | if (text.trim()) { 234 | children.push(`"${this.encodeString(text.trim())}"`); 235 | } 236 | } 237 | } 238 | 239 | // Skip closing tag 240 | this.pos += tagName.length + 3; // 241 | 242 | if (children.length === 0) { 243 | output += `, null)`; 244 | } else if (children.length === 1) { 245 | output += `, ${children[0]})`; 246 | } else { 247 | output += `, [${children.join(", ")}])`; 248 | } 249 | } 250 | 251 | return output; 252 | }, 253 | 254 | compile() { 255 | let output = ""; 256 | while (this.pos < this.length) { 257 | if (this.peek() === "<") { 258 | output += this.parseTag(); 259 | } else { 260 | // Handle potential emoji in non-tag content 261 | const code = this.code.codePointAt(this.pos); 262 | if (code > 0xffff) { 263 | output += this.code.slice(this.pos, this.pos + 2); 264 | this.pos += 2; 265 | } else { 266 | output += this.advance(); 267 | } 268 | } 269 | } 270 | return output; 271 | }, 272 | }; 273 | 274 | function compile(code, replaceFunc) { 275 | return new JSXCompiler(code, replaceFunc).compile(); 276 | } 277 | 278 | if (typeof module !== "undefined") { 279 | module.exports = compile; 280 | } else { 281 | window.compileJSX = compile; 282 | } 283 | })(typeof self !== "undefined" ? self : this); 284 | -------------------------------------------------------------------------------- /src/ssreact.js: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | // Set up React global 3 | global.React = { 4 | ...preact, 5 | useState: preactHooks.useState, 6 | useEffect: preactHooks.useEffect, 7 | useRef: preactHooks.useRef, 8 | useCallback: preactHooks.useCallback, 9 | useMemo: preactHooks.useMemo, 10 | }; 11 | 12 | // Process JSX scripts with proper UTF-8 handling 13 | function processScripts() { 14 | const scripts = document.querySelectorAll('script[type="text/babel"]'); 15 | scripts.forEach((script) => { 16 | try { 17 | console.log(script.textContent); 18 | 19 | const compiledCode = window.compileJSX(script.textContent, "React.h"); 20 | console.log(compiledCode); 21 | const newScript = document.createElement("script"); 22 | // Set UTF-8 charset on the new script 23 | newScript.charset = "utf-8"; 24 | newScript.textContent = compiledCode; 25 | 26 | script.parentNode.replaceChild(newScript, script); 27 | } catch (error) { 28 | console.error("Error compiling JSX:", error); 29 | console.error("Original content:", script.textContent); 30 | console.error("Decoded content:", decodeUTF8(script.textContent)); 31 | } 32 | }); 33 | } 34 | 35 | // Mount components 36 | function mountComponents() { 37 | const components = document.querySelectorAll('[id^="ssreact."]'); 38 | components.forEach((element) => { 39 | const componentName = element.id.split(".")[1]; 40 | const Component = global[componentName]; 41 | if (typeof Component === "function") { 42 | React.render(React.h(Component), element); 43 | } else { 44 | console.warn( 45 | `Component "${componentName}" not found for element with id "${element.id}"`, 46 | ); 47 | } 48 | }); 49 | } 50 | 51 | // Initialize when DOM is ready 52 | if (document.readyState === "loading") { 53 | document.addEventListener("DOMContentLoaded", () => { 54 | processScripts(); 55 | mountComponents(); 56 | }); 57 | } else { 58 | processScripts(); 59 | mountComponents(); 60 | } 61 | })(typeof self !== "undefined" ? self : this); 62 | -------------------------------------------------------------------------------- /src/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JSX Simple React + Tailwind 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 34 |

Click to increment ✅ ...

35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "janwilmake_ssreact" 2 | compatibility_date = "2024-12-05" 3 | assets = { directory= "./" } 4 | routes = [ 5 | { pattern = "ssreact.com", custom_domain = true }, 6 | { pattern = "www.ssreact.com", custom_domain = true } 7 | ] --------------------------------------------------------------------------------