waterfromleaves

Stay for some tea, will you?

  • She/They

Plural. Queer. Gideon and Iris
Avatar by @phimeirai



Osmose
@Osmose

Phantomake is a static site generator that I've been working on for the past few weeks. It's a single binary with no dependencies that you can use to generate your website. It lets you apply templates to your HTML pages, reduce copy-pasting by reusing common snippets of code, convert markdown files to HTML, and more. You can use Phantomake on an existing static website without any extra setup and only generate the pages you need.

Phantomake is useful for almost any static site, but specifically made for indie web / small web sites that are already hand-writing HTML and want straightforward improvements like includes or pagination.

Because Phantomake is built with Bun, it is only available on MacOS and Linux for now—Windows will be supported as soon as Bun makes an official Windows release. Bun landed Windows support a while ago and Phantomake is now available as an EXE for running on the command line!

It's open source and available on Github. Bug reports or fixes are welcome!

Feedback wanted!

Phantomake is pretty fresh and I'm using it for my own site and the tool's documentation, but I would love any feedback on how good or bad it works for you! Feature requests are welcome, just keep in mind that the tool is intentionally simple and requests may not be entertained depending on their complexity.

But please, if you're interested, try it out and let me know if it helps you!


Osmose
@Osmose

Reminder if you're getting back into making your own website that I wrote a static site generator with the best logo and you should use it and ask me about it

EDIT: lmao adding windows support took 5 mins and I did it from a Macbook, enjoy


You must log in to comment.

in reply to @Osmose's post:

i am a total novice to making websites (very much wanting to learn but not having the foundation to know where to look or what to look for) so i'm hoping i can parse this! :yeah:

I'm a big fan of 32-Bit Cafe's tutorial on how to make your own website, depending on how much progress you've already made in making one: https://32bit.cafe/cyowebsite/

Once you've made several pages and are starting to wonder stuff like "how do I avoid copy-pasting the same header/footer code in every separate HTML file" that's about the point where a tool like Phantomake will start being useful.

i am having that problem of "ugh god okay let me go grab my last post to get the header/footer info"
i'm definitely going to investigate this 32bit cafe tutorial first, but i'll let you know when/if i circle around to phantomake again! thank you for the recc :eggbug-smile-hearts:

in reply to @Osmose's post:

oh wow, this is almost exactly what i've been looking for in an ssg. it's so ... simply scoped (positive). everything else i've looked at was just overkill for personal website/blog stuff, by trying to do everything, 11ty probably came closest but even then.
at the most basic level of just using this as an alternative for includes instead of relying on having php around would be great. i'm not the most technical sort so we'll see how long it takes me to figure it out, also i'm still stuck on windows but i suspect WSL can resolve that issue for now! i can't javascript my way out of a wet paper bag but i might not need to, from the looks of things.

Good news I added windows support so you can grab an EXE directly now

But yeah I tried to make the docs a little nicer for folks not used to things like the CLI but willing to learn, and the Examples section in particular is meant to be something you can semi-intelligently copy-paste from for your needs. Let me know how it goes!

that is the best logo

we're almost certainly going to write our own in the near future (for, like, probably the third time or so, we've lost count)

but we almost want to try yours just because of the logo. it's really good

I am unable to run the windows build on my PC, I get a "The procedure entry point GetThreadDescription could not be located in the dynamic link library" error. Is there some dependency that I'm missing, or does it require a newer version of windows? (I am on a somewhat older version but I have not had this error before)

I'm seeing a bunch of stuff saying "GetThreadDescription" got added in Windows 10, and bun also says it requires a minimum of Windows 10. I'm guessing you're on an older version than that?

It doesn't have any more output than that, I even checked the event log and couldn't see anything that seemed more helpful (but I also don't really know what to look for). I am on Windows 10 Build 14393.

EDIT: Googling found "The GetThreadDescription API is available since Windows 10, version 1607."

1607 is Build 14393, but they probably changed it somehow in future updates in a way that broke it for me. So this is probably just a me problem that doesn't need fixing on your end.

I've been tinkering and learning how to use this, and I have a few questions if you have time! To preface, all my webdev experience is in static HTML pages and CSS, but I have a few years of programming experience thanks to Unity and C#. I'm just not all that familiar with JS, so I'm trying to learn how Phantomake's EJS integration works.

One feature I'm working towards is a gallery page. I have entries from two separate folders (basically, "single images" and "project pages"), and I want to render the two types of entries together as one big list. The approach I had in mind was to create two separate lists for each type of entry, a third list that combines and sorts the entries together, and then render the HTML for each entry depending on what type it is.

I was able to hack together a simple tiny gallery using ctx.paginate as a proof of concept, though it only used one source folder. It functions, but I'm sure there's a better way. 💦

So, questions!

  • Is there a way to create a list without using pagination?
  • Can a new list be created using other lists as sources?
  • Can a generated list be referenced across multiple pages, without having to regenerate it for each page that it appears on?
    (eg: a list of "projects" that appears on project pages, or previous/next buttons for blog posts)

Sure! Note that I haven't tested this code so I might get it a bit wrong, but if you struggle to get it working still let me know and we can figure it out.

Is there a way to create a list without using pagination?

Yep! You can use ctx.getFiles to get a list of files without using the pagination functions:

<%
const singleImages = ctx.getFiles('single-images-dir/*.png');
const projectPages = ctx.getFiles('project-pages-dir/*/index.md');
%>

Can a new list be created using other lists as sources?

Yep again! JavaScript has spread syntax that can be used to, among other things, combine two lists together:

<%
const singleImagesAndProjectPages = [...singleImages, ...projectPages];
%>

Can a generated list be referenced across multiple pages, without having to regenerate it for each page that it appears on?

Ohhhh, this one's tough. You can't.

I can think of two ways to do the aforementioned next/previous buttons but they're both annoying:

  1. Use an include to include a "next page" or "prev page" helper. Pass some sort of ID or timestamp to the helper that identifies the including page. The helper will use ctx.getFiles to build the list of all files, then locate the file in that list matching the including page, and then render a button pointing to the previous or next page from the list.
  2. Use a allPages.json.ejs file to generate a blob of JSON with all the posts in it. Then, add JavaScript to your webpage that reads in that file and injects the next/prev buttons.

I'll think about whether we should add, like, a ctx.import for importing, like, JS files into templates, but I'm not confident it's both possible and worth adding.

Let me know if that helps, thanks for trying out Phantomake!

That helps tremendously, thank you! The list stuff was the one big uncertainty, but now I've got a list full of mixed types sorting and rendering correctly. 💪

As for the nav buttons... I'm realizing I could probably just get a little tricky with the build process: Set up a separate build to generate a HTML for navigation, and use it as an .include in the main build. (and then just set up a .bat file to run both builds sequentially, so I can never forget to run them.)

It's really cool to see these puzzle pieces fall into place, so I'm excited to try and build a full site out of this. Thanks again!

Hey hi I saw on at least one post that you asked about people using Phantomake and since the clock is ticking and I’m not sure if I’ll be able to put in the time next weekend, I’m going to just make a giant mess in your comments while I still can. I thought about making an actual regular cohost post about it but honestly given the way I’ve used cohost blogging in your comments is the most appropriate way to go about it, probably. I’ll do the experiential narrative bit up top and questions/hopes/whatever at the end!

Stage setting: I’m a non-coder, but I am a glorified end user. Been computering and online for far too long and my ‘technical’ experience is largely limited to being able to follow tutorials well, have setup linux vps’ for servers for various things, but bad at troubleshooting and have zero actual programming experience. Got pretty good at being able to modify eggdrop bot scripts in the IRC days but that’s all dead knowledge. Command lines are fine and good, but everything involving repos and whatever is… doable but I have no idea what’s really going on, it’s just a tutorial trust fall exercise.

Other stage setting: I haven’t made anything yet. That’s the hard part! I’m still doing the fun tools exploration part. (it has been extremely fun)

Anyway I was getting to the point mentally where I was just going to start putting together a stupid website in the artisanal hand-HTML fashion of my youth because I didn’t want to mess with wordpress or anything of the sort any more and just do plain old static pages. The thing I kept thinking was how I really just wanted PHP include so I could at least make a navbar and a footer (miss you, frames). I kept looking at static site generators but every single one was absolutely bonkers for anything I had in mind and everything had way more presumption of being used by a ‘coder’ and not an ‘end user’, even a slightly technical one.

So when I saw the Phantomake post I was elated since in its most basic form I could just use it as a glorified PHP include to spit out a finished version of whatever I put together with some small template bits I could edit for global changes. And it’s just a single executable! (yeah sure so is Hugo but much like the rest of the field, Hugo is terrifying) And you put together that Windows binary mere moments after my discovery, so I didn’t even have to open a WSL terminal. Basically Christmas.

Started working through the examples. The Includes are really straightforward, exactly what I was hoping for initially, and then some. Haven’t played with templating much yet because design is not my passion but I have no concerns about figuring it out.

Started to stretch a touch when re-implementing the blog example. Reconfigured it slightly to account for hosting that is setup to do slugs/”pretty urls”, or whatever that’s all called, instead of doing the endless folder/index.html thing. Took me a while but I figured it out! Then I started thinking about how the pagination could be used to do so much other stuff, like handling serialized stories by using 1 item per page and using a unique folder or filename structure to pull the relevant pieces with getFiles.

Then I got to thinking about how the ability to make custom indices and such with the paginator could also be really useful too, and not all that hard.

At some point in this process I got to thinking about tags and wondered if I really cared. My initial plan wasn’t going to allow for them anyway, so not having them shouldn’t be a barrier, and the more I thought about it the less I kind of cared for personal web page stuff. Would I really ever be that prolific? I think some of the use cases could just be accommodated for via YAML stuff and custom indices anyway, for things I really cared about, if I decided to? Keeping some tags in the YAML just for my own internal use is probably the most practical use for anything I’d be doing anyway… let’s be real, my mess isn’t that complicated.

The RSS bit is the only piece I haven’t had time to fully chew over. I got the basic example running fine with minor edits, but got to thinking about more complicated use cases that I’m not even sure I can articulate well. I guess considering a hypothetical world where the website was updating both a blog and other serialized side projects, but wanted all the updates to be centralized in a single RSS feed, but have some one type of content be full text and the other type be YAML slugs.

Anyway, long story long, been poking around with it for several hours this weekend and have had an extremely enjoyable time. The whole thing felt very within my grasp in a way that every other SSG I’ve tried to use did not. Even when I’d started to get them working, just using them still felt like too much work. I genuinely haven’t had this much fun just noodling around with web stuff since I was 16 and found out my dialup provider gave us a whole five megs of web hosting. Phantomake rules and you could change nothing going forward and it’d still be tremendously useful. Thank you so much!

Okay second post with specific questions generated by my assorted daydreams while playing with Phantomake:

  1. With the Pagination, is there any way to have it also generate relative ‘next’ and ‘previous’ links, in addition to and/or instead of just a list of page numbers? Like a page.next and page.previous situation? Or is this solvable with what is already there and I’m just not catching it?

  2. I have only the barest experience with YAML so this is dumb question territory, but are there technical restrictions on what can be included? Is it limited to predefined variables? Thinking of use cases like identifiers for different page types, or aforementioned tagging situation. I noticed your YAML examples weren’t enclosed in quotes so I’m wondering if a space separated tags entry would even be useful for evaluation purposes (can you partial-match YAML variable contents?), or if I should treat it more as a category designation and stick to one item for that use.

  3. Kind of piggybacking on the above, but thinking about ways to do weird RSS things, but I noticed you had the if/then/else in the example about running javascript. Again, no functional coding knowledge, but would I be barking up the wrong tree trying to nest an if/else sort of conditional inside the normal rss “for” loop that looked for a piece of YAML defining a post type and did one thing for one and another for another? I guess the thought is having Phantomake just scan the whole website and all dirs when making the RSS but only trigger an addition to the generated xml if certain YAML variables were matched.

I think that’s it? It’s more than enough. You can totally ignore all of this too I don’t truly need tech support this is all just weird thoughts I was having because I was excited noodling with a new thing that I could actually wrap my head around for once. It has been a novel weekend.

edit: lol right after posting this i read through the new comments and saw Lollie asking about basically the same thing i did in q1, and while the proposed suggestion is prooobably outside of my grasp it's nice to get affirmation that the question wasn't totally misguided. :eggbug: not having any js background is definitely narrowing my scope of imagination here, i'm thinking.

  1. The YAML frontmatter gets parsed and stripped from files before any other template logic runs, so you can't use EJS within it. It also does not allow unsafe YAML features like running logic. Otherwise, all standard YAML data structures should work, like lists and such. If you look up a YAML cheat sheet it should clarify what's supported.
  2. Yup you can put an if stament within a loop to treat different matched files differently.