What is constexpr?

Constexpr is a feature found in statically compiled programming languages that allows evaluation of expressions in a program at compile time. Different languages have different terminology for it. For example, in zig lang, it's called comptime. Common lisp macros, in lisps that compile to native code, might also be grouped in this category.

This feature allows automatic generation of complex static data at compile time, and the runtime code can just use the result of compile time evaluated data. Variable inlining is a simple form of compile time evaluation.
Ideally, the language/compiler should allow the users to turn any eligible piece of code "constexpr" with some annotations. The eligibility criteria being that the code must not depend on any runtime data.

What is constexpr.js?

constexpr.js is a tool that allows you to execute parts of javascript in your website before deployment. You can use it like a static site generator, as I do for this website. It's different from SSG's like Jekyll in that it doesn't force you to learn a dedicated domain specific language. There already exists a dedicated languages for dealing with DOM and webpages, javascript. With constexpr.js, you use javascript and usual DOM manipulation methods to generate the website. The whole browser runtime is available at your disposal when generating sites with constexpr.js.

Demo

This whole website is built using constexpr.js.

How does it work?

The compiler renders the pages using chrome, and once they finish rendering, it saves the rendered state as new pages. It also strips out the javascript that was used for generating HTML, potentially reducing download size for the website users drastically. Any piece of javascript code that just generates some HTML can be made constexpr.

The generated pages don't have to be completely static. For example, the login form ui, mobile view navigation, and disqus integration in this page use javascript.

The new website will look exactly like the original website after the pages finish rendering. This is one of the basic principles of constexpr.js. So you can build your website as usual and run the compiler once you're happy with the results, and you'll get a leaner, faster version of your website as output.

How to use it?

The compiler can be installed from npm:

npm i -g constexpr.js@latest

Command line usage: $ constexprjs --help usage: constexprjs [-h] [-v] --input INPUT_DIRECTORY --output OUTPUT_DIRECTORY [--entry ENTRYPOINTS] [--skip-resources] [--jobcount JOBCOUNT] [--jobtimeout JOBTIMEOUT] [--depfile DEPFILE] [--noheadless] [--verbose] Zero cost abstractions for web development optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit --input INPUT_DIRECTORY Input website root directory --output OUTPUT_DIRECTORY Output directory --entry ENTRYPOINTS Add an HTML file to be used as entry point, paths must be relative to the website root, can be used multiple times, must provide at least one entry point --skip-resources Do not copy resources to the output directory --jobcount JOBCOUNT Number of compilation jobs to run in parallel --jobtimeout JOBTIMEOUT Time in milliseconds for which the compiler will wait for the pages to render --depfile DEPFILE A JSON object containing the command line arguments, file dependency, compilation results will be written to this path --noheadless Do not run chrome in headless mode, can be used for debugging using browser console --verbose Enable verbose logging

This is what an invocation looks like:

~/src/fctorial.github.io.src $ ./devtools/build.sh --verbose --noheadless Using port:------------------------------------------------- 9046 Using job count:-------------------------------------------- 10 Using job timeout:------------------------------------------ 999999999 Queued file #1:--------------------------------------------- /index.html /index.html added extra path /projects.html to be generated using /projects.html /index.html added extra path /tags.html to be generated using /tags.html /index.html added extra path /404.html to be generated using /404.html /index.html added extra path /posts/intellij_logos.html to be generated using /posts/intellij_logos.html /index.html added extra path /posts/modifying_idea.html to be generated using /posts/modifying_idea.html /index.html added extra path /posts/constexpr.js.html to be generated using /posts/constexpr.js.html /index.html added extra path /posts/runtime_compiletime_constexpr.js.html to be generated using /posts/runtime_compiletime_constexpr.js.html /index.html added extra path /posts/zircon_standalone.html to be generated using /posts/zircon_standalone.html /index.html added extra path /posts/constexprjs_generator_pages.html to be generated using /posts/constexprjs_generator_pages.html /index.html added extra path /posts/youtube_python_bug.html to be generated using /posts/youtube_python_bug.html /index.html added extra path /posts/syntax_highlighting_nojs.html to be generated using /posts/syntax_highlighting_nojs.html /index.html added extra path /posts/constexprjs_api_docs.html to be generated using /posts/constexprjs_api_docs.html /index.html added extra path /posts/ui_api.html to be generated using /posts/ui_api.html /index.html added extra path /posts/constexprjs_entry_points.html to be generated using /posts/constexprjs_entry_points.html /index.html added extra path /posts/dynamic_colored_border.html to be generated using /posts/dynamic_colored_border.html /index.html added extra path /posts/wisdom_teeth.html to be generated using /posts/wisdom_teeth.html /index.html added extra path /posts/tst.html to be generated using /posts/tst.html /index.html added extra path /posts/rust_difficult.html to be generated using /posts/rust_difficult.html (1/19) Finished:-------------------------------------------- /index.html Queued file #2:--------------------------------------------- /projects.html Queued file #3:--------------------------------------------- /tags.html Queued file #4:--------------------------------------------- /404.html Queued file #5:--------------------------------------------- /posts/intellij_logos.html Queued file #6:--------------------------------------------- /posts/modifying_idea.html Queued file #7:--------------------------------------------- /posts/constexpr.js.html Queued file #8:--------------------------------------------- /posts/runtime_compiletime_constexpr.js.html Queued file #9:--------------------------------------------- /posts/zircon_standalone.html Queued file #10:-------------------------------------------- /posts/constexprjs_generator_pages.html Queued file #11:-------------------------------------------- /posts/youtube_python_bug.html (2/19) Finished:-------------------------------------------- /projects.html Queued file #12:-------------------------------------------- /posts/syntax_highlighting_nojs.html /tags.html added extra path /tags/web.html to be generated using /tags/generator.html?web /tags.html added extra path /tags/constexpr.js.html to be generated using /tags/generator.html?constexpr.js /tags.html added extra path /tags/essay.html to be generated using /tags/generator.html?essay /tags.html added extra path /tags/intellij.html to be generated using /tags/generator.html?intellij /tags.html added extra path /tags/ui.html to be generated using /tags/generator.html?ui /tags.html added extra path /tags/zircon_standalone.html to be generated using /tags/generator.html?zircon_standalone /tags.html added extra path /tags/python.html to be generated using /tags/generator.html?python /tags.html added extra path /tags/css.html to be generated using /tags/generator.html?css /tags.html added extra path /tags/health.html to be generated using /tags/generator.html?health /tags.html added extra path /tags/clojure.html to be generated using /tags/generator.html?clojure /tags.html added extra path /tags/testing.html to be generated using /tags/generator.html?testing /tags.html added extra path /tags/rust.html to be generated using /tags/generator.html?rust (3/31) Finished:-------------------------------------------- /tags.html Queued file #13:-------------------------------------------- /posts/constexprjs_api_docs.html (4/31) Finished:-------------------------------------------- /404.html Queued file #14:-------------------------------------------- /posts/ui_api.html (5/31) Finished:-------------------------------------------- /posts/youtube_python_bug.html Queued file #15:-------------------------------------------- /posts/constexprjs_entry_points.html (6/31) Finished:-------------------------------------------- /posts/zircon_standalone.html Queued file #16:-------------------------------------------- /posts/dynamic_colored_border.html (7/31) Finished:-------------------------------------------- /posts/modifying_idea.html Queued file #17:-------------------------------------------- /posts/wisdom_teeth.html (8/31) Finished:-------------------------------------------- /posts/intellij_logos.html Queued file #18:-------------------------------------------- /posts/tst.html (9/31) Finished:-------------------------------------------- /posts/runtime_compiletime_constexpr.js.html Queued file #19:-------------------------------------------- /posts/rust_difficult.html (10/31) Finished:------------------------------------------- /posts/constexprjs_generator_pages.html Queued file #20:-------------------------------------------- /tags/web.html (11/31) Finished:------------------------------------------- /posts/syntax_highlighting_nojs.html Queued file #21:-------------------------------------------- /tags/constexpr.js.html (12/31) Finished:------------------------------------------- /posts/ui_api.html Queued file #22:-------------------------------------------- /tags/essay.html (13/31) Finished:------------------------------------------- /posts/constexprjs_api_docs.html Queued file #23:-------------------------------------------- /tags/intellij.html (14/31) Finished:------------------------------------------- /posts/constexprjs_entry_points.html Queued file #24:-------------------------------------------- /tags/ui.html (15/31) Finished:------------------------------------------- /posts/wisdom_teeth.html Queued file #25:-------------------------------------------- /tags/zircon_standalone.html (16/31) Finished:------------------------------------------- /posts/dynamic_colored_border.html Queued file #26:-------------------------------------------- /tags/python.html (17/31) Finished:------------------------------------------- /posts/tst.html Queued file #27:-------------------------------------------- /tags/css.html (18/31) Finished:------------------------------------------- /tags/generator.html?web Queued file #28:-------------------------------------------- /tags/health.html (19/31) Finished:------------------------------------------- /posts/rust_difficult.html Queued file #29:-------------------------------------------- /tags/clojure.html (20/31) Finished:------------------------------------------- /tags/generator.html?constexpr.js Queued file #30:-------------------------------------------- /tags/testing.html (21/31) Finished:------------------------------------------- /tags/generator.html?essay Queued file #31:-------------------------------------------- /tags/rust.html /tags/generator.html?zircon_standalone: only one post found with tag: "zircon_standalone" /tags/generator.html?python: only one post found with tag: "python" (22/31) Finished:------------------------------------------- /tags/generator.html?intellij (23/31) Finished:------------------------------------------- /tags/generator.html?ui (24/31) Finished:------------------------------------------- /tags/generator.html?zircon_standalone (25/31) Finished:------------------------------------------- /tags/generator.html?python /tags/generator.html?css: only one post found with tag: "css" /tags/generator.html?health: only one post found with tag: "health" /tags/generator.html?clojure: only one post found with tag: "clojure" (26/31) Finished:------------------------------------------- /tags/generator.html?css (27/31) Finished:------------------------------------------- /tags/generator.html?health (28/31) Finished:------------------------------------------- /tags/generator.html?clojure /tags/generator.html?testing: only one post found with tag: "testing" /tags/generator.html?rust: only one post found with tag: "rust" (29/31) Finished:------------------------------------------- /tags/generator.html?testing (30/31) Finished:------------------------------------------- /tags/generator.html?rust (31/31) Finished:------------------------------------------- /posts/constexpr.js.html Wrote depfile:---------------------------------------------- devtools/deps.json Copying resource:------------------------------------------- /static/rss.xml Copying resource:------------------------------------------- /favicon.ico Excluding resource:----------------------------------------- /collections/config.json Excluding resource:----------------------------------------- /collections/posts.json Excluding resource:----------------------------------------- /collections/nav.json Copying resource:------------------------------------------- /static/img/bg.jpg Copying resource:------------------------------------------- /static/css/styles.css Copying resource:------------------------------------------- /static/img/logos/twitter.svg Copying resource:------------------------------------------- /static/img/logos/facebook.svg Copying resource:------------------------------------------- /static/img/logos/reddit.svg Copying resource:------------------------------------------- /static/img/logos/github.svg Copying resource:------------------------------------------- /static/img/logos/email.svg Excluding resource:----------------------------------------- /collections/projects.json Copying resource:------------------------------------------- /static/css/prism.css Copying resource:------------------------------------------- /static/img/fuchsia.svg Excluding resource:----------------------------------------- /collections/intellij_logos.json Copying resource:------------------------------------------- /static/img/intellij_logos/2009.09.01.png Copying resource:------------------------------------------- /static/img/intellij_logos/2009.09.09.png Copying resource:------------------------------------------- /static/img/intellij_logos/2009.09.13.png Copying resource:------------------------------------------- /static/img/intellij_logos/2009.11.03.png Copying resource:------------------------------------------- /static/img/intellij_logos/2010.00.11.png Copying resource:------------------------------------------- /static/img/intellij_logos/2010.06.22.png Copying resource:------------------------------------------- /static/img/intellij_logos/2010.10.16.png Copying resource:------------------------------------------- /static/img/intellij_logos/2010.10.30.png Copying resource:------------------------------------------- /static/img/intellij_logos/2011.00.14.png Copying resource:------------------------------------------- /static/img/intellij_logos/2011.06.19.png Copying resource:------------------------------------------- /static/img/intellij_logos/2011.06.22.png Copying resource:------------------------------------------- /static/img/intellij_logos/2011.08.05.png Copying resource:------------------------------------------- /static/img/intellij_logos/2011.10.15.png Copying resource:------------------------------------------- /static/img/intellij_logos/2011.11.01.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.00.10.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.03.23.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.05.27.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.10.07.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.10.28.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.10.30.png Copying resource:------------------------------------------- /static/img/intellij_logos/2012.11.26.png Copying resource:------------------------------------------- /static/img/intellij_logos/2013.00.14.png Copying resource:------------------------------------------- /static/img/intellij_logos/2013.04.15.png Copying resource:------------------------------------------- /static/img/intellij_logos/2013.04.22.png Copying resource:------------------------------------------- /static/img/intellij_logos/2013.10.04.png Copying resource:------------------------------------------- /static/img/intellij_logos/2013.10.27.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.00.21.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.01.07.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.02.05.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.02.11.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.03.07.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.08.28.png Copying resource:------------------------------------------- /static/img/intellij_logos/2014.09.20.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.03.29.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.04.13.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.04.19.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.08.04.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.09.21.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.11.03.png Copying resource:------------------------------------------- /static/img/intellij_logos/2015.11.17.png Copying resource:------------------------------------------- /static/img/intellij_logos/2016.04.11.png Copying resource:------------------------------------------- /static/img/intellij_logos/2016.06.19.png Copying resource:------------------------------------------- /static/img/intellij_logos/2016.11.13.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.03.18.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.05.26.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.05.27.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.05.28.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.05.30.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.08.05.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.08.06.png Copying resource:------------------------------------------- /static/img/intellij_logos/2017.09.24.png Copying resource:------------------------------------------- /static/img/intellij_logos/2018.00.22.png Copying resource:------------------------------------------- /static/img/intellij_logos/2018.04.14.png Copying resource:------------------------------------------- /static/img/intellij_logos/2018.08.08.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.00.14.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.03.04.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.04.07.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.04.10.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.04.29.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.05.08.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.05.12.png Copying resource:------------------------------------------- /static/img/intellij_logos/2019.08.02.png Copying resource:------------------------------------------- /static/img/intellij_logos/2020.00.21.png Copying resource:------------------------------------------- /static/img/intellij_logos/2020.04.21.png Copying resource:------------------------------------------- /static/img/intellij_logos/2020.08.16.png Copying resource:------------------------------------------- /static/img/intellij_logos/2021.00.21.png Copying resource:------------------------------------------- /static/img/win8.jpg Copying resource:------------------------------------------- /static/img/wisdom_troll.jpg Copying resource:------------------------------------------- /static/css/pivot.css Copying resource:------------------------------------------- /static/css/asciinema-player.css Copying resource:------------------------------------------- /static/packages/katex/katex.css Copying resource:------------------------------------------- /static/img/stones.jpg Copying resource:------------------------------------------- /static/packages/katex/fonts/KaTeX_Main-Regular.woff2 Copying resource:------------------------------------------- /static/packages/katex/fonts/KaTeX_Math-Italic.woff2 ~/src/fctorial.github.io.src $  
00:11-00:00

The tool also copies resources (css, images etcetra) that are requestead by pages being rendered (unless --skip-resources option is specified.

See this page for a hello world demo of constexpr.js.

Plugins

You can use any web development technology (and any number of technologies) to generate the HTML without any fear of bloat. Pivottable.js demo:

dayFriSatSunThurTotals
sexsmoker
FemaleNo6.2535.4246.6161.49149.77
Yes18.7843.0314.0020.9396.74
MaleNo5.00104.21133.9658.83302.00
Yes21.9377.7452.8230.58183.07
Totals51.96260.40247.39171.83731.58

This page also uses prism.js for syntax highlighting, katex for math formulae, viz.js for graphs and asciinema-player for the ./build.sh output, along with jquery and papaparse. A total of 6mb of javascript that you don't have to download or execute because it's constexpr.

Performance

The compiler doesn't have any noticeable overhead. So the compilation time depends on how fast chrome can render all the pages in your website:

compilation_timeaverage_rendering_time×total_number_of_pagesjob_countcompilation\_time \approx { average\_rendering\_time \times {total\_number\_of\_pages \over job\_count} }

You can increase the job count using --jobcount parameters if you have a ton of cores. The sweet spot is around 1.5X the number of cores/threads.

However, compilation time shouldn't be an issue regardless of the size of your website. Because, as I mentioned above, the original website (the one you write) will look and work exactly the same as the generated website, so you will only have to run the compiler once per deployment.

On my 4 core / 4 thread 3.5 GHz machine, the amortized compilation time is around 100ms per page, and it should scale down linearly with the number of cores/threads/jobs. That puts its performance in the same ballpark as Jekyll.

If that doesn't work for you, you can use --entry option to compile a small number of pages in your website. See this guide for more information.

There is also an incremental compilation feature in the pipeline. It should be out by the time the page count of this website hits 100 pages (currently at 47).

Notes

  1. You can mark tags other than script with constexpr as well. Add this code to your page to differentiate original page from the generated page: <style constexpr> body { border: 2px solid red; } </style> This code will add a red border to your page which will only appear on the original website.
  2. In the original webpage, you'll see a console error when the code tries to call the compilation trigger functions. Because those functions are injected by the compiler. You can add this snippet to fix that error: <script constexpr> if (!window._ConstexprJS_) { window._ConstexprJS_ = { compile: () => {}, abort: () => {}, addPath: () => {}, addExclusion: () => {}, addDependency: () => {}, log: () => {} } } </script>
  3. You can manage multiple rendering task in your page using promises: Promise.all([render_task_1(), render_task_2()]) .then(() => window._ConstexprJS_.compile())
  4. You should keep all list data separate from the html in json files. constexpr javascript should fetch these json files and render the page using them.
    You can even use a real database if you're thinking about scalability. constexpr.js gives you a turing complete templating language, so you can do anything you want.
  5. This whole website is rendered using javascript and constexpr.js. The html files contain only the page specific stuff. (article text and page specific styling). All of the styling and theming is done by constexpr code. The whole website contains 44 lines of javascript (for disqus and some optional dynamic functionality). The original sources for this website can be found here.
  6. Use this if you love javascript
  7. Use this if you hate javascript
See pages tagged with constexpr.js for more guides.