I built a framework: M(e)nimal static site generator

Sandro Maglione

Sandro Maglione

Web development

This week I implemented a new javascript framework (or course) πŸ§ͺ

Well, probably not exactly a full framework, but more a minimal static site generator written with Typescript and Effect πŸ’‘

Menimal static site generator 🀏 HTML + CSS only generated from Markdown files πŸ’πŸΌβ€β™‚οΈ No need of package.json, javascript, or whatever πŸͺ„ Deploy HTML on @vercel with ease

Image
Image
Image
Image
Guillermo Rauch
Guillermo Rauch
@rauchg

Pretty funny timing. I came across a really slick project on HN, and it's an `index.html` on @vercel 😁 To answer @deepfates question: β—† Install our github/gitlab apps and `git push` β—† Or `echo '<marquee>hi' > index.html && npx vercel`

2
Reply

It wasn't too difficult after all, this is how I did it πŸ‘‡


Tech stack

  • Typescript: plain Typescript, all bundled and released as a single javascript file on npm
  • Effect: all services organized and managed using Effect (convert markdown to html, transform and minimize css, frontmatter, and more)

Setup

Typescript-only project, easy to configure:

  • package.json
  • tsconfig.json
  • src containing ts files
  • Extra configurations (if needed) and generated folders

Nowadays it's way easier than ever to create a Typescript project πŸ’πŸΌβ€β™‚οΈNowadays it's way easier than ever to create a Typescript project πŸ’πŸΌβ€β™‚οΈ

Get started

Goal for this project: make a minimal static site generator 🀏

The key word is Minimal: the focus should be on writing content.

Zero javascript, no html, only content (md files) and styling (css) ☝️

Sounds easy? Well, not exactly πŸ˜…

During the implementation many interesting ideas come to mind. It's not always easy to stop adding features πŸ’πŸΌβ€β™‚οΈ

I managed well at the end πŸ‘‡

There is more.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.

Implementation

Effect is awesome for writing executable scripts πŸ”₯

  • Implement each feature in a separate service
  • Connect all the services in the entry file
  • Provide dependencies, handle errors, run the app
bin.ts
#!/usr/bin/env node
 
const program = Effect.gen(function* (_) {
  /// 1️⃣ Use all the services to implement the final program
});
 
const MainLive = Layer.mergeAll(
  /// 2️⃣ Collect and provide all dependencies (services implementation)
);
 
const runnable = program.pipe(
  Effect.provide(MainLive)
);
 
const main: Effect.Effect<never, never, void> = runnable.pipe(
  Effect.catchTags({
    /// 3️⃣ Handle all possible errors (logging)
  })
);
 
/// 4️⃣ Run the final app
Effect.runPromise(main).catch(console.error);

All the details of the implementation are defined in separate files πŸ‘‡

Create and implement a service for each feature: parsing, logging, file system, cssCreate and implement a service for each feature: parsing, logging, file system, css

How to generate static sites

The idea is simple:

  • Write all the content in markdown files (md)
  • Read all the files as string
  • Convert markdown to html

Done πŸ’πŸΌβ€β™‚οΈ

Map all the markdown files to html pages, that's allMap all the markdown files to html pages, that's all

I then added some convenient features:

  • static files (robots.txt, favicon.ico)
  • Styling (single style.css file)
  • Frontmatter
  • Check valid links

This is all handled at build time, no javascript in the final website ⏱️

Don't reinvent the wheel

There is a library for everything (especially in javascript πŸ˜…)

I didn't implement any parsing, converter, validator, or anything:

"dependencies": {
  "@effect/platform": "^0.43.7",
  "@effect/platform-node": "^0.42.7",
  "@effect/schema": "^0.61.5",
  "effect": "^2.2.3",
  "chalk": "^4.1.2", // πŸ—³οΈ Logging
  "gray-matter": "^4.0.3", // πŸ“ Frontmatter
  "html-minifier": "^4.0.0", // 🀏 Minify html
  "lightningcss": "^1.23.0", // 🀏 Transform css
  "mustache": "^4.2.0", // πŸ—οΈ HTML templates
  "node-html-parser": "^6.1.12", // 🧱 Validate links
  "showdown": "^2.1.0" // πŸ‘· Markdown to HTML
}

These dependencies do the heavy lifting. Effect collects and organizes all together:

bin.ts
const program = Effect.gen(function* (_) {
  const fileSystem = yield* _(FileSystem.FileSystem);
  const css = yield* _(Css.Css);
  const template = yield* _(Template.Template);
  const converter = yield* _(Converter.Converter);
  const siteConfig = yield* _(SiteConfig.SiteConfig);
  const linkCheck = yield* _(LinkCheck.LinkCheck);
 
  yield* _(Effect.logInfo("Start build"));
  /// Do the work πŸ—οΈ

Publish library on npm

Last step: bundle everything and publish npx executable script πŸš€

Tweet not found

The embedded tweet could not be found…

I used tsup. This was easy as well, just run a single tsup command:

"scripts": {
  "tsc": "tsc -p tsconfig.json",
  "dev": "tsx src/bin.ts",
  "bundle": "tsup && tsx scripts/copy-templates.ts", 
  "upload": "pnpm bundle && npm publish"
}

Note: The hard part was finding the right libraries, make them work well together, and handle all the configuration πŸ€”

Everything is out there, it's mostly about knowing where to search for things (and ask for help)Everything is out there, it's mostly about knowing where to search for things (and ask for help)


I wrote a complete step-by-step article on how to bundle and publish an npx command on npm.

This is not always easy, but the result is super satisfying πŸ”₯

Takeaways

  • You can build anything: it's all about managing complexity and use the right tools πŸ› οΈ
  • No need to be complicated: there is probably a library out there that already solved the same problem
  • Effect is the ultimate meta-library: ideal to manage and organize all dependencies in any app
  • Going minimal pays off: start with the basics, then add features later if necessary

You can read the full code on the open source repository πŸ‘‡

I plan to add more features as needed in the future (and share all the progress of course πŸ’‘)

See you next πŸ‘‹

Start here.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 700+ readers.