The page you are looking at is now powered by Vite. This is something I want to do for a long while since Vite came out, and it’s finally done. As I have mentioned in my first blog post, it was powered Gridsome using Vue 2. With this overhaul, I can now take full advantage of Vue 3 and the Composition API with the new <script setup>
SFC style.
The reason for it taking me so long to do this is because I am busy (enjoy) doing yak shaving, for the tools I need to build this site.
Dark Mode
Dark mode is supported as an experimental feature in Tailwind CSS v1.8 and shipped in v2.0. It supports two modes for you to choose from - media
and class
. media
is something that works out-of-box, it changes based on users’ system preference. But the limitation is that you can’t toggle it manually as you want. So I went with class
mode where I have more controls over it. But that also means I would need to implement the toggling logic myself.
With the power of Vue’s Composition API, I am able to combine the best parts of them - reactive to the system’s preference while being able to override manually.
import { useStorage, usePreferredDark } from '@vueuse/core'
const preferredDark = usePreferredDark()
const colorSchema = useStorage('color-schema', 'auto')
export const isDark = computed({
get() {
return colorSchema.value === 'auto'
? preferredDark.value
: colorSchema.value === 'dark'
},
set(v: boolean) {
if (v === preferredDark.value)
colorSchema.value = 'auto'
else
colorSchema.value = v ? 'dark' : 'light'
},
})
watch(
isDark,
v => document.documentElement.classList.toggle('dark', v),
{ immediate: true },
)
Click it to try 👇🏼
If you would like to use it in your own apps, I also extract the logic above into useDark()
in VueUse. Where you can simply use like this:
import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark()
const toggleDark = useToggle(isDark)
Markdown
After building Icônes, I started working on the Codecember project with @octref, an initiative of learning and creating generative arts in December. With the spirit of dogfooding, we chosen Vite for building the site. In Codecember we will need to have a prompt every day with some texts, code snippets, and demos. This comes with the problem that Vite does not have a plugin for handling markdown files at that moment, so of course, I made one myself.
- vite-plugin-md - Markdown for Vite.
Basically, it uses markdown-it
to transform markdown into HTML and feed it into Vue’s template compiler. As the generated template is handled by Vue, we can easily support Vue components inside Markdown.
Syntax Highlighting
Getting syntax highlight works in dark mode isn’t an easy task as well. Shiki inlined all the colors into the HTML so you would not be bored by the CSS namespace pollution, but that also means it will be really hard to get the colors aware of your global color scheme. Prism on the other hand, uses the classes combining the CSS theme to do the job. It’s easier to merge two color schemes and make them aware of the dark
trigger. The bad thing is, themes are often wrote by different authors with different styles of coloring and styling things. Sometimes, even the font and spacing could be different across different themes. If you ever ran into a similar situation, you should know what I mean. If you don’t (lucky you!), see Prism’s themes collection(prism-vs.css
and prism-vsc-dark-plus.css
for example).
Fight with them for a while you might be able to ease the misalignment eventually. But what if we can have a smarter way to do this?
- prism-theme-vars - A customizable Prism.js theme using CSS variables.
Instead of dealing with the lengthy CSS theme, now you can have one in less than 20 lines of CSS variables. For example:
@import "prism-theme-vars/base.css";
:root {
--prism-foreground: #393a34;
--prism-background: #fbfbfb;
--prism-comment: #b8c4b8;
--prism-string: #c67b5d;
--prism-literal: #3a9c9b;
--prism-keyword: #248459;
--prism-function: #849145;
--prism-deleted: #a14f55;
--prism-class: #2b91af;
--prism-builtin: #a52727;
--prism-property: #ad502b;
--prism-namespace: #c96880;
--prism-punctuation: #8e8f8b;
}
To have it supports dark mode is extremely simple as well:
html:not(.dark) {
--prism-foreground: #393a34;
--prism-background: #f8f8f8;
--prism-comment: #758575;
--prism-namespace: #444444;
--prism-string: #bc8671;
--prism-punctuation: #80817d;
--prism-literal: #36acaa;
--prism-keyword: #248459;
--prism-function: #849145;
--prism-deleted: #9a050f;
--prism-class: #2b91af;
--prism-builtin: #800000;
}
html.dark {
--prism-foreground: #d4d4d4;
--prism-background: #1e1e1e;
--prism-namespace: #aaaaaa;
--prism-comment: #758575;
--prism-namespace: #444444;
--prism-string: #ce9178;
--prism-punctuation: #d4d4d4;
--prism-literal: #36acaa;
--prism-keyword: #38a776;
--prism-function: #dcdcaa;
--prism-deleted: #9a050f;
--prism-class: #4ec9b0;
--prism-builtin: #d16969;
}
That’s all. You can also play with the themes in the Playground and make some your own within 5 mins. I created my first code theme in my life using it, which is also exactly what you are looking at :)
Serve-Side Generatation (SSG)
While Codecember is more like a site than an app, we would need to do some server-side generation to improve our SEO. Read quite a lot of code from VitePress, I came up with this plugin:
- vite-ssg - Server-side generation for Vite.
The idea here is fairly simple: bundle the app entry, then for each route, dump the app using APIs from the @vue/server-renderer
package. Simplified code here:
import { renderToString } from '@vue/server-renderer'
const createApp = required('dist-ssr/app.js')
await Promise.all(
routes.map(async(route) => {
const { app, router, head } = createApp(false)
router.push(route)
await router.isReady()
const appHTML = await renderToString(app)
const renderedHTML = renderHTML(indexHTML, appHTML)
await fs.writeFile(`${route}.html`, renderedHTML, 'utf-8')
})
)
The full code can be found here.
With the @vueuse/head package made by @egoist, I made the document head/meta manipulation in SSG with ease. Combining with vite-plugin-md, you can even use the frontmatter to set the meta (title, description, og:image, etc.).
<script setup>
import { useHead } from '@vueuse/head'
useHead({
title: 'Website Title',
meta: [
{
name: 'description',
content: 'Website description',
},
],
})
</script>
The Vite Template
I found myself making small web apps frequently. Setting up plugins and configs for Vite kinda becomes the bottleneck for me to make my idea landded. So combining with those tools I am using, I made an opinionated template for myself but unexpectedly got quite some good feedback:
- Vitesse - Opinionated Vite Starter Template
This Website
This site is made from Vitesse combining with all the tools I mentioned above. To be honest, even making a static site generator right is something hard to me, not to mention that most of the hard parts are already handled by Vite. I am really happy to see so many things I have learned and crafted along the way. And glad I can make these contributions to the Vite ecosystem, that someone could find my work useful for building their apps.
Thanks
I can’t make all these happened without the help/support from the great community, thank y’all!
Also want to have some special thanks to people made significant contributions towards these projects 🙌 (A-Z)
And thank you for reading through!