Skip to content

Instantly share code, notes, and snippets.

@loreanvictor
Last active January 25, 2024 16:38
Show Gist options
  • Save loreanvictor/936bffc2403f7e07e4b263f1e7d0977f to your computer and use it in GitHub Desktop.
Save loreanvictor/936bffc2403f7e07e4b263f1e7d0977f to your computer and use it in GitHub Desktop.
Pre: a no build build tool

pre

No build is cool and all, but two things are inherently missing (and will remain missing):

  • Web components are rendered client side
  • No markdown support

Solution

The first issue can be resolved with the following simplistic tool:

  • Emulate a web browser
  • Load the pages
  • Export the HTML with declarative shadow DOM
  • Save it for static serving somewhere

This also requires an emulated server, where code and assets used by the components are served, and hence can be loaded by the emulated browser and executed (so we get pre-rendered web components).

Markdown support can then be provided with web components. Such components can also distinguish whether they are being rendered for the first time or not, so they could avoid running the preprocessing on the client side. They even could, though perhaps not conveniently, avoid loading the supplementary code for such preprocessings.

That said, it would be greatly more convenient in component development to load some web components (some script, basically) only on the emulated browser. This can be semantically achieved with an attribute on script tags, e.g. build-only (<script build-only src="..."></script>), which would cause such scripts to be stripped from the final DOM (how about shadow DOM?). Since this is a custom attribute ignored by the browsers, the ensuing HTML will still be completely valid and standard-compliant HTML.

Important

An important aspect of this scenario is that ALL of the component code and scripts and what not will remain browser compliant as they are in the end loaded by an emulated browser that mimics the behavior of a real browser. This, for example, means ESM only, plain CSS, unbundled JavaScript, etc. It is, in the end, a no build solution, only pre-rendering web components.

Example

<!-- pages/index.html -->
<head>
  <script build-only src="../node_modules/@pre/components/include.js"></script>
  <script src="../components/side-bar.js"></script>
</head>
<body>
  <pre-include src="../layout/header.html"></pre-include>

  <pre-include src="../content/index.md"></pre-include>
  <side-bar></side-bar>
  
  <pre-include src="../layout/footer.html"></pre-include>
</body>
<!-- content/index.md -->

<script build-only src="../node_modules/@pre/components/admonition.js"></script>

# Hellow World!

<admon-warning>
  <span slot="title">Just An Idea</span>
  
  This is just an idea right now. Maybe it will look pretty different on the final solution.
</admon-warning>

Other Considerations

Build Env

It would be perhaps exceedingly useful if components and scripts could access information about the build environment, for example listing all files and folders in the project path, reading environment variables, or reading git information. This would allow the web components to provide many of the useful features of build tools.

The emulated server could provide APIs for such information. For example, @fs/ls?pattern="**/*.md" (URL encoded) would provide a list of all files in the project folder matching given pattern, or @git/author/email would provide the email address of the project author, etc.

The benefit of this solution would be that such requests would simply fail on the client side, which means components can gracefully handle the difference in environment. It might also be useful to allow the components to check support for such APIs in a faster, more synchronous manner (for example, a global flag, instead of waiting for a request to fail).

While such access provides great benefits, it also raises some concerns:

  • The components utilising such APIs would become stack-dependent and not standard-compliant.
  • The accessed information should generally not be accessible in a raw form on the client side for security reasons. Some components might mistakenly cache or otherwise store the uncleaned data though, causing security issues.

Verbosity, Layouting & Frontmatter

In most cases, the project includes multiple HTML pages, potentially using similar layouts, assets, etc. With this solution, each of those pages needs to be individually written in a particularly verbose matter.

  • The head part needs to be repeated as web components can't load assets for the parent DOM.
  • The layouts can be done with web components, but it might be slower to load all their styles (potentially? needs to be tested perhaps)

This could be solved by supporting frontmatter on the HTML files. This turns them into non-standard files, but allows handling layouts, assets and metadata quite conveniently:

---
title: My Page
layout: ../layouts/main.html
---

<pre-md>
  
# Hellow World!
  
This is a fun thing to do isn't it?
  
</pre-md>

This takes away from the simplicity of the stack but makes working with these files pretty convenient. Without such feature, in practice we would need other tools to handle these concerns. Another tool added on top of pre would basically be yet another toolchain, which is against the idea of this tool.

Performance

Running an emulated browser for pre-rendering can be a bit tricky. I hope something like Happy DOM does the trick as it is fast, as something like Puppeteer might be too slow for the job. In the end though, performance is not a goal of this project and if we can reach acceptable performance then all should be fine. In some cases, maintaining compatibility with features of real browsers might actually be worth some loss in performance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment