โ€ข

newsletter

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

This is how I built a minimal static site generator in Typescript using Effect: how a static site generator works, how it's implemented, and how to publish it to npm as an npx command.


Sandro Maglione

Sandro Maglione

Software development

This week I implemented a new javascript framework (of 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 ๐Ÿคฉ

Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.

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 ๐Ÿš€

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 ๐Ÿ‘‡

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.

Timeless coding principles, practices, and tools that make a difference, regardless of your language or framework, delivered in your inbox every week.