Build static websites using ReactJS and ConstexprJS

Top

I use ReactJS and ConstexprJS to render this static website. The React code is evaluated and stripped at compile time and only static HTML is left which is what you see in your browser right now. In this article I'll share some tips for using ReactJS with ConstexprJS.

I'll refer to the implementation of this website as an example. For the impatient folks, here is the commented javascript code that renders this website.

In the below explanation, I've reordered/refactored the code blocks a little to make them easier to understand.

Setup

  • All the required libraries are fetched and evaluated: await evalScript('/static/js/constexpr/lib.js') await evalScript('/static/js/constexpr/third_party/lodash.js') await evalScript('/static/js/constexpr/third_party/react.js') await evalScript('/static/js/constexpr/third_party/react-dom.js')
  • An alias is defined to make react programming easier: window.e = React.createElement With this in place, we can use the following vanilla javascript: e( SvgIcon, { className: 'open', alt: 'open left sidebar', backgroundSvg: context.icons.swipe } ) To render this JSX component: <SvgIcon className="open" alt="open left sidebar", backgroundSvg={context.icons.swipe} />
  • Wait until react is done rendering the page: await new Promise((resolve) => { ReactDOM.createRoot(contentRoot) .render(e(Page, {pageHasRendered: () => resolve()}, null)) }) The top level component, Page, calls the provided callback in an effect: React.useEffect(() => { pageHasRendered() }, []); Which means the promise will resolve once react has rendered the site.

Data store

All the external resources are provided to the rendering code using a react context: let PageResources ... PageResources = React.createContext() ... ReactDOM.createRoot(contentRoot) .render(e( PageResources.Provider, {value: fetchResources()}, e(Page, {}) ))

These resources include site data (the website url, top nav items list, projects list, posts list along with tags, etc), inline styles/svg for performance, etc.

This context is consumed by whatever component needs to access this data: e( PageResources.Consumer, {}, // Essential CSS embedded in HTML instead of <link rel="stylesheet"> for performance context => e('style', {}, context.mainCss) ) ... e( PageResources.Consumer, {}, (context) => e( 'nav', {}, context.nav .map(item => e( 'a', {href: item.href, key: item.href, className: item.href === window.location.pathname ? 'current' : ''}, item.name )) ) ) ... e( PageResources.Consumer, {}, (context) => e( SvgIcon, { mask: true, backgroundSvg: context.icons.moon, id: 'toggle_theme', style: {width: '1.3em', height: '1.3em'} } ) )

How does ConstexprJS compare to Next.js, Qwik.js, ...?

  • ConstexprJS is not bound to a web development framework like ReactJS. It's just a tool for evaluating javascript in a website ahead of time. Nothing is stopping you from using jquery, vanilla javascript, or anything else with it.
  • All of these frameworks render the React components in Node.js using DOM emulation libraries like jsdom. ConstexprJS renders your websites in a headless chrome instance, which means you can use any JS library and it's guaranteed to work exactly like it does in a browser.
  • Next.js and Qwik.js project templates are preconfigured to support contemporary React development practices, like JSX and Typescript. These features are out of ConstexprJS's scope. However, plans to introduce NPM project templates with sample configurations are in the pipeline.
  • These frameworks support automatic hydration. Hydration has to be done manually in ConstexprJS. ( /posts/constexprjs_code_organization.html)
  • ConstexprJS gives you fine-grained control over what code you want to run at compile time. It's determined by the constexpr code in your website.
See pages tagged with constexprjs for more guides.