Hacker News new | past | comments | ask | show | jobs | submit login
Static Site Generator in 86 Lines (alexxx.co)
48 points by malkosta on April 8, 2022 | hide | past | favorite | 62 comments



I use Python for that. It's just a few lines of code

template.html:

  <header><h3>%(header)s</h3></header>
  <div>%(content)s</div>
  <footer>%(footer)s</footer>
generate.py:

  content = """# This is a markdown content"""
  from markdown import markdown
  with open('output.html', 'w') as f:
    f.write(open('template.html').read() % {
      'header': 'welcome',
      'content': markdown.markdown(content),
      'footer': 'amazing company inc'
    })
for serving it:

  python3 -m http.server
it will be running on 127.0.0.1:8000


Python is severe overkill here. You only need "cat" and maybe "sed" for that.


Surely it is possible to do it with sed, but can you show me, how?


Something like that might work I guess

cat template.html | sed s/content/$(markdowncompiler content.md)/


That’s a sed-injection bug.


The best kind of bugs!


I wanted to be able to create new layouts without touching the precompiler. To rely only on HTML/CSS/Markdown to code the frontend. You can't code partials within the HTML file with your solution.


Actually it's possible too

  class partial(dict):
  ...   def __getitem__(self, k):
  ...     return open(k).read()
So we will render the template like this

template.html:

  <header><h3>%(header)s</h3></header>
  <div>%(content)s</div>
  <footer>%(footer.html)s</footer>
generate.py

  content = """# This is a markdown content"""
  with open('output.html', 'w') as f:
    f.write(open('template.html').read() %  partial({
      title: 'Velkommen',
      content: markdown.markdown('markdown content')
    }))


My static site generator is 5500 lines of Java, because the only thing I hate more than software I didn't invent myself is technologies that are fashionable ;-)


Sure, but you could build the same thing with <100 LOC, in JS, 10 years ago.

What I’m trying to say is that even if this qualifies as fashionable in your time scale, a decade is a lot.

This isn’t criticism of your approach. It’s better to stick to whatever works for you and focus on the problem domain rather than the tech stack.


To be fair, 10 years is still uncomfortably modern in my book. My tech needs to at least be old enough to drive a car. Ideally it should be old enough to have an existential crisis about how its life is turning out.

... and you can probably build a 100 LOC static page generator in Java as well.

My thing does a lot of weird crap, though. Like it also acts as a Gemini protocol server, you can publish from the web and it pushes every change to a git repo. It also tracks backlinks and re-publishes every page that is mentioned. It also renders from a specialized version of gemtext so there really isn't any standard libraries to use (that I'm aware of).


> .. and you can probably build a 100 LOC static page generator in Java as well.

Hehe, given how verbose Java can get, I’d increase the word wrap limit x3 in that case.

But yeah, I see your point and the project sounds pretty cool. Can I find it anywhere?


My dude, them's fighting words. So I built a SSG in 50 lines of mostly idiomatic Java (one line is a bit squished, but 52 just doesn't sound as cool):

https://github.com/vlofgren/50-line-java-ssg

Does deal with partials, bit stuff like an automatic index requires maybe 5-10 additional lines.

Took about an hour (plus 15 minutes to try to figure out how to get into my github account, which I haven't actively used in the last 15 odd years).

My "real" Java SSG is used to generate https://memex.marginalia.nu/. Unfortunately I don't have the source published.


Here is the shortest I can get while keeping markdown support - 3 lines, with 1 dependency:

  for file in *.md; do
    pandoc --quiet --template layout.html $file -o "${file%.*}.html"
  done


You're wearing out your enter key my man

  $ find -maxdepth 0 -name \*md -exec sh -c 'pandoc --quiet --template layout.html "$0" -o ${0%.*}.html' {} \;


Hahaha cool!


I liked!


Posts like this demonstrate to me that JavaScript is the modern Perl. The gigantic, vibrant module ecosystem and ease of installing them* is what makes JavaScript/Node.js my default system for getting stuff done quickly.

(* = Well, it was easy, but I feel like this ESM business now means npm/yarn install doesn't Just Work™ in some cases.)


I'm not entirely sure if this is meant sarcastically.


even if it is sarcastic, I still agree with them. I know it's cool to hate javascript but it's not going anywhere.


> I know it's cool to hate javascript but it's not going anywhere.

This does seem to align with it being a modern perl


I agree. Sure there are issues but basically node/js is my scripting language. It used to be python but I do so much more JS that I just end up doing everything I can there.


I am perhaps nitpicking, but I expected it to be 86 lines without dependencies (or including the dependencies LOC in the total!)


That's possible, not sure if I want. I would have to:

1. drop markdown support, and posts in HTML

2. find a way to parse HTML via regex, which is doable if I use a different notation than a tag for `<partial src=...`

3. use a language that has filesystem support natively, to read files without a dependency

I'm working on 2 and 3. But I don't want to drop markdown support on the blog posts...so I guess I will live with this :)


Agreed. Or it could be one line of code by extracting it to a lib.


Everyone should make their own Static Site Generator. It is really fun.

I made mine with Python and which is uses Github pages -

- Jinja templating engine

- Markdownpy

- Subprocess (for git CLI commands)

- And a classless CSS of your choice


This is what JSX is great at, and it's why I wrote a new web framework that basically just uses JSX for server-side views, that work the way I always thought JSX should: it just returns an object that you can then render into a string if you want or transform first. This is also how I made it so you can put <script> and <style> tags into any component, and it'll de-dup them before rendering, so that you can keep your component scripts and HTML close by each other in source.


People is reinventing PHP xD


Nice and simple. One note to the author - you could reduce your line count quite a bit and get a bunch of features for free by using Nunjucks (https://mozilla.github.io/nunjucks/) instead of parsing the template yourself. It handles your partials and variables in the same way plus a whole lot more.


Do you really save lines of code if you add another dependency? Many lines of code are coming with that...


Always a tradeoff, but the author is already using these dependencies:

    const parse = require('node-html-parser').parse;
    const beautify = require('js-beautify').html;
    const loadYaml = require('js-yaml').load;
    const showdown = require('showdown');


That's possible, not sure if I want. I would have to:

1. drop markdown support, and posts in HTML

2. find a way to parse HTML via regex, which is doable if I use a different notation than a tag for `<partial src=...`

3. use a language that has filesystem support natively, to read files without a dependency

I'm working on 2 and 3. But I don't want to drop markdown support on the blog posts...so I guess I will live with this :)


Yes I wanna get rid of all dependencies...just finding my way through it.


An unfortunate choice of domain name. Many web filter vendors block it due to the "xxx" in the FQDN, including the one at my work.



Oh, good to know! I had no idea about this...


Fixed that for you in 2 lines of GNU Make:

    %.html: %.m4
        m4 -P < $< > $@


How does it handle the most important part?

  <partial src="partials/layout.html">
    My fancy content
  </partial>


  for file in *.md; do
    pandoc --quiet --template layout.html $file -o "${file%.*}.html"
  done


Change the \t to a ; and see it become a single line


I really like the fact that partials are handled as HTML-like markup instead of the HTML just happening to be within some other template system. Kudos on that design choice!


Thanks! That made it easier to handle this usecase:

  <partial src="partials/layout.html">
    My fancy content
  </partial>
Because I can use HTML parsers. But now I'm thinking of a way to remove all dependencies, to I will have to get rid of html parser, and find a way to those substitutions without parsing HTML, with just regex replacement.


I built a very similar markdown => static site generator[1].

It was 100 lines. But a little deceptive, since like yours it relied on a markdown parsing library.

The bulk of the code was handling creating a sitemap.xml and all the of meta/opengraph/twitter tags.

[1]https://blog.rysolv.com/markdown-static-site-generator


That's nice! Completely different approach...


Also see tinyjam[0] by Volodymyr Agafonkin[1].

[0] https://github.com/mourner/tinyjam [1] https://agafonkin.com/


Hey guys,

I wrote this very simple SSG, and would like to receive feedback on how to simplify code.

Also, I would be happy to fix any bug.


In your post you write:

> Just like every dev writing a blog, my first question was: What is the minimum set of features to maintain it comfortably?

Then you created a program in JS that requires NodeJS to run. But cool programs written in JS don't require NodeJS when it's not necessary. They just run in the browser.

PS: Really cool static site generators that shoot for simplicity don't require you to create extra template files written in a new, made-up template language. When you want to create a new post, you give it (a) the static files from your existing site and (b) the markdown for your new post. The "templating" engine inspects your existing posts (incl. e.g. class attributes) and then copies the same document structure into a new file, except with the right stuff (timestamp, title and heading, post content...) substituted in to the places where it's supposed to go.


Something like this does the job for you...basically applies the template and whatever metadata you give to the .md file.

  for file in *.md; do
    pandoc --quiet --template template.html $file -o "${file%.*}.html"
  done
But I wanted a flexible way to reuse components. So I need the <partial> trick to support outer and inner partials in the reusable components. For example a form that I want to reuse on some pages, or a custom call to action.


Sorry, I don't think you understand. First, I'm not looking for help finding a tool that does what I describe. Second, that Pandoc invocation doesn't do what I describe. I'm saying no template file.

Let's say you have static site today with your lost post being "2022/03/31/working-on-a-new-tool.html". To author your new post, the tool could derive the template based on what the actual, HTML-formatted version of your last post looks like, and then it would substitute the new post content into the appropriate places. For example, where your previous post has a date stamp indicating it was published on March 31, the tool would put the date of the new post. Where your previous post has the title in an H1 or whathaveyou, your new title would appear. Where the main text of your last post appears, the main text of your new post appears. This would all be written into a new file. There's no need for an explicit template file.

PS: Pandoc doesn't run in the browser, either. You're already going to have your browser open to check the output. Its runtime is both powerful enough, and it's right there, so use it. No need to bring other runtimes into this.


Got any links for some of the "Really cool static site generators"?


Yes. I'm looking to remove NodeJS dependency, but I will probably have to switch languages since JS itself doesn't let me read files from disk, to run the preprocessing.



Nice work on this! I'd suggest publishing to npm so that it can be run with npx.

Check out fncli if you want to add some command line args... might be nice to specify some of the config that way


My needs might be different than yours. For me personally, and being a fan of Jekyll, I come to expect the following from any static site generator:

1. Layouts / Template Inheritance

2. Sass Compiler

3. Live Reload

Love the baseline set of tools here though!


Live Reload should be easy. I prefer pure CSS and pure HTML. But opened an exception for Markdown, for the sake of writing posts. I don't know what you mean by 1, I thought I achieved that with:

  <partial src="partials/layout.html">
    My fancy content
  </partial>


tangential, but what do people generally do when they want mostly a static site, but a small amount of dynamic content/interactivity here and there? Seems like a common use-case that as sites grow they might want a little bit of not-static here and there.

Obviously not that difficult to add some javascript and APIs if you want, which avoids the need to mix in a different stack while generating the same look-and-feel. Or you can use NGINX/Apache frontend to redirect certain paths to a more traditional dynamic stack/CMS of course. Just curious if there's any "standard" approach or off-the-shelf tooling for that for (eg) Jekyll or similar.


Vercel (with or without Next) is my stack of choice for projects like this.

You can get a site running in Vercel+Next in <2m from ‘git init’.

I use it for https://potato.horse and most PoCs now, but we’ve scaled Vercel+Next to hundreds of thousands of users per day in other projects.

You could also use Netlify with Netlify functions, although I prefer the (slightly) less opinionated way of handing releases and per-commit vanity URLs Vercel offers.

Aaand if you want to get more funky with your approach, check out Astrojs (island architecture: start with static content and sprinkle the interactions on top of it).

You could also try, Elixir + Phoenix + Live View if you want to learn a new tech stack, feed your brain, and… spend a few days yak shaving. Nope, none of the 5 new versions of your blog will be live by the time you’re done, but you’ll become good friends with Erlang and co. Speaking from the experience, I use next when I want my stack to be boring.


It depends *where* said dynamic content/interactivity is desired. If its desired at the page level, such as one page is desired to be dynamic...Then i keep everything else produced by my static site generator (in my case Pelican), and then set up a subdirectory and use php. (PHP because pretty much any web host, VPS provider etc. either already comes with PHP or its pretty easy to setup. But also because i faovr php development for the web.) Your comment about leveraging redirecting to certain paths via nginx/apache is something i've done in the past also...but unless you have a nice (automated, or at least easy) workflow...that could get old fast.

...However, if what you want is dynamic content/interactivity within the context of a page that is served static from your SSG process, then I (reluctantly) resort to javascript (Sorry, my reluctance is because i'm not much of a fan of javascript...and in fact have tried to omit as much of it as reasonable from my projects.)


I'm in a totally different competition: how to get sh*t done quickly, get others to handle the hard parts and get good maintainability so that I can go home sooner?


Read Hugo/Wordpress documentation takes longer than write something like this. I got this blog running faster that I would if I had to learn a new tool.


I can hang 32 terminals off one PC.


Something something rediscovering XSLT.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: