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
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`
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
containingts
files- Extra configurations (if needed) and generated folders
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
#!/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, 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 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:
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 ๐
๐ก Project update ๐ก I published a npx script made with @EffectTS_ ๐ฅ Entry file that runs Effect โ FileSystem โ Logging โ Static site generator All the details coming next week ๐
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)
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 ๐