Skip to content

Commit

Permalink
Add blog and first post to website
Browse files Browse the repository at this point in the history
  • Loading branch information
fabian-hiller committed Mar 1, 2024
1 parent 689770e commit b997a0f
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 2 deletions.
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@
"og-img": "^0.2.0",
"postcss": "^8.4.35",
"rehype-external-links": "^3.0.0",
"sharp": "^0.33.2",
"tailwindcss": "^3.4.1",
"tsm": "^2.3.0",
"typescript": "^5.3.3",
"undici": "^6.6.2",
"vite": "^5.1.3",
"vite-imagetools": "^6.2.9",
"vite-tsconfig-paths": "^4.3.1"
},
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion website/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export const Header = component$<HeaderProps>(({ searchOpen }) => {
>
{[
{ label: 'Guides', href: '/guides/' },
{ label: 'API reference', href: '/api/' },
{ label: 'API', href: '/api/' },
{ label: 'Blog', href: '/blog/' },
{ label: 'Playground', href: '/playground/' },
].map(({ label, href }) => (
<Link
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export * from './Header';
export * from './IconButton';
export * from './MainMenuToggle';
export * from './Navigation';
export * from './Property';
export * from './RoutingIndicator';
export * from './SearchToggle';
export * from './SideBar';
Expand All @@ -20,4 +21,3 @@ export * from './SystemIcon';
export * from './TextLink';
export * from './ThemeToggle';
export * from './UnstyledButton';
export * from './Property';
7 changes: 7 additions & 0 deletions website/src/routes/blog/(posts)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { component$, Slot } from '@builder.io/qwik';

export default component$(() => (
<main class="mdx flex w-full max-w-screen-lg flex-1 flex-col self-center py-12 md:py-20 lg:py-32">
<Slot />
</main>
));
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
cover: API Design
title: Should we change Valibot's API?
description: >-
Today I am writing this message to you to discuss the further development of Valibot's API.
published: 2024-02-29
authors:
- fabian-hiller
- Demivan
- xcfox
---

import NpmDownloads from './npm-downloads.jpg?jsx';

# Should we change Valibot's API?

Hi folks, I am [Fabian](https://twitter.com/FabianHiller), the creator and maintainer of Valibot. Today I am writing this message to you to discuss the further development of Valibot's API. With currently more than 80,000 weekly downloads, the library is growing into a serious open source project used by thousands of JavaScript and TypeScript developers around the world.

<NpmDownloads alt="Daily downloads from August 2023 to March 2024" />

In the last few days I received two [API proposals](https://github.com/fabian-hiller/valibot/discussions/453) from [@Demivan](https://github.com/Demivan) and [@xcfox](https://github.com/xcfox). Both of them addressed similar pain points of Valibot's current API design. As our v1 release is getting closer and closer, it is important to me to involve the community in such a big decision.

## Current pain points

Valibot's current mental model is divided into schemas, methods, validations, and transformations. Schemas validate data types like strings, numbers and objects. Methods are small utilities that help you use or modify a schema. Validations and transformations are used in the `pipe` argument of a schema. They can make changes to the input and check other details, such as the formatting of a string.

```ts
// With Valibot's current API
const EmailSchema = string([toTrimmed(), email(), endsWith('@example.com')]);
```

A drawback of this API design is that the current pipeline implementation is not modular. This increases the initial bundle size of simple schemas like [`string`](https://valibot.dev/api/string/) by more than 200 bytes. This is almost 30% of the total bundle size.

Another pain point is that the current pipeline implementation does not allow you to transform the data type. This forced me to add a [`transform`](https://valibot.dev/api/transform/) method, resulting in two places where transformations can happen.

```ts
// With Valibot's current API
const NumberSchema = transform(string([toTrimmed(), decimal()]), (input) => {
return parseInt(input);
});
```

Speaking of methods, it can quickly become confusing if you need to apply multiple methods to the same schema, as these functions must always be nested.

```ts
// With Valibot's current API
const LengthSchema = brand(
transform(optional(string(), ''), (input) => input.length),
'Length'
);
```

The last pain point that comes to mind is that the current API design gives you less control over the input and output type of a schema. When working with form libraries, it can be useful to have an input type of `string | null` for the initial values, but an output type of just `string` for a required field.

## The `pipe` function

After several design iterations, [@Demivan](https://github.com/Demivan) came up with the idea of a `pipe` function. Similar to the current `pipe` argument, it can be used for validations and transformations. The first argument is always a schema, followed by various actions or schemas.

```ts
// With the new `pipe` function
const LoginSchema = object({
email: pipe(string(), minLength(1), email()),
password: pipe(string(), minLength(8)),
});

// With Valibot's current API
const LoginSchema = object({
email: string([minLength(1), email()]),
password: string([minLength(8)]),
});
```

The big difference is that the `pipe` function also allows you to transform the data type. This would allow us to move methods like [`transform`](https://valibot.dev/api/transform/) and [`brand`](https://valibot.dev/api/brand/) into the pipeline. This prevents nesting of functions for these methods and simplifies the mental model.

With the `pipe` function, the mental model is reduced to schemas, methods, and actions. Actions are always used within the `pipe` function to further validate and transform the input and type of a schema.

> Alternative names for `pipe` are `flow` (idea by [@mtt-artis](https://github.com/mtt-artis)), `schema` (idea by [@genki](https://github.com/genki)), and `compose` (idea by [@MohammedEsafi](https://github.com/MohammedEsafi)), and `vali` (idea by [@Hugos68](https://github.com/Hugos68)). Please share your thoughts.
## The advantages

The `pipe` function makes Valibot even more modular, resulting in a smaller bundle size for very simple schemas without a pipeline. For very complex schemas it reduces function nesting when using [`transform`](https://valibot.dev/api/transform/) and [`brand`](https://valibot.dev/api/brand/).

```ts
// With the new `pipe` function
const LengthSchema = pipe(
optional(string(), ''),
transform((input) => input.length),
brand('Length')
);

// With Valibot's current API
const LengthSchema = brand(
transform(optional(string(), ''), (input) => input.length),
'Length'
);
```

It also gives you more control over the input and output type, and simplifies the mental model by eliminating the confusion between the [`transform`](https://valibot.dev/api/transform/) method and the pipeline transformations of the current API.

```ts
// With the new `pipe` function
const NumberSchema = pipe(
string(),
toTrimmed(),
decimal(),
transform(parseInt)
);

// With Valibot's current API
const NumberSchema = transform(
string([toTrimmed(), decimal()]),
parseInt
);
```

Besides that, the `pipe` function would also allow us to easily add a [metadata feature](https://github.com/fabian-hiller/valibot/issues/373) to Valibot. This could be interesting when working with databases to define SQL properties like `PRIMARY KEY`.

```ts
// With the new `pipe` function
const UserSchema = pipe(
object({
id: pipe(string(), uuid(), primaryKey()),
name: pipe(string(), maxLength(32), unique()),
bio: pipe(string(), description('Text ...')),
}),
table('users')
);
```

## The disadvantages

The main disadvantage of the `pipe` function is that it requires a bit more code to write for medium sized schemas, and for more complex schemas you may end up nesting multiple pipelines.

```ts
// With the new `pipe` function
const NumberSchema = pipe(
union([pipe(string(), decimal()), pipe(number(), integer())]),
transform(Number)
);

// With Valibot's current API
const NumberSchema = transform(
union([string([decimal()]), number([integer()])]),
Number
);
```

## Let's discuss

Your opinion matters to me. I encourage everyone to share their thoughts. Even quick feedback like "I like this ... and I don't like that ..." is welcome and will help to shape Valibot's future API design. Please discuss with me on [GitHub](https://github.com/fabian-hiller/valibot/discussions/463) or share your thoughts on [Twitter](https://twitter.com/FabianHiller/status/1763253086464639035).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions website/src/routes/blog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { component$ } from '@builder.io/qwik';
import { Link } from '@builder.io/qwik-city';
import clsx from 'clsx';

export default component$(() => (
<main class="flex w-full max-w-screen-lg flex-1 flex-col self-center py-12 md:py-20 lg:py-32">
<div class="mdx">
<h1>Blog</h1>
<p>
Official announcements, project updates and insightful content directly
from the Valibot core team. We're excited to share our journey with you!
Let's validate together!
</p>
<h2>Latest posts</h2>
</div>
<ol class="mx-3 mt-6 flex flex-wrap lg:mx-2 lg:mt-10">
{[
{
cover: 'API Design',
title: "Should we change Valibot's API?",
published: '2024-02-29',
authors: ['fabian-hiller', 'Demivan', 'xcfox'],
href: './should-we-change-valibots-api/',
},
].map((post) => (
<li class="w-full px-5 py-6 md:w-1/2 lg:p-8" key={post.href}>
<Link class="group" href={post.href}>
<div class="relative flex aspect-video items-center justify-center overflow-hidden rounded-xl border-2 border-slate-200 duration-100 group-hover:-translate-y-1 lg:rounded-2xl dark:border-slate-800">
<div class="absolute -right-20 -top-36 h-[400px] w-[250px] bg-[radial-gradient(theme(colors.yellow.500/.06),transparent_70%)] dark:bg-[radial-gradient(theme(colors.yellow.300/.04),transparent_70%)]" />
<div class="absolute -bottom-36 -left-20 h-[400px] w-[250px] bg-[radial-gradient(theme(colors.sky.600/.08),transparent_70%)] dark:bg-[radial-gradient(theme(colors.sky.400/.06),transparent_70%)]" />
<div class="font-lexend-exa text-center text-2xl font-medium text-slate-700 lg:text-3xl dark:text-slate-300">
{post.cover}
</div>
</div>
<h3 class="mt-8 text-xl leading-normal text-slate-900 lg:text-2xl dark:text-slate-200">
{post.title}
</h3>
<div class="mt-5 flex items-center space-x-4">
<div class="-m-0.5 flex">
{post.authors.map((author, index) => (
<img
class={clsx(
'box-content w-6 rounded-full border-[3px] border-white lg:w-7 dark:border-gray-900',
index > 0 && '-ml-3'
)}
style={{ zIndex: post.authors.length - index }}
key={author}
width="56"
height="56"
src={`https://github.com/${author}.png?size=56`}
alt={`GitHub profile picture of ${author}`}
/>
))}
</div>
<time class="text-sm lg:text-base" dateTime={post.published}>
{new Date(post.published).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</time>
</div>
</Link>
</li>
))}
</ol>
</main>
));
5 changes: 5 additions & 0 deletions website/src/styles/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@
@apply focus-ring rounded-md text-sky-600 underline decoration-slate-400 decoration-dashed underline-offset-[3px] focus-visible:outline-offset-4 focus-visible:ring-offset-[6px] dark:text-sky-400 dark:decoration-slate-600;
}

/* Images */
.mdx img {
@apply w-full rounded-2xl border-2 border-slate-200 lg:rounded-3xl lg:border-[3px] dark:border-slate-800;
}

/* Lists */
.mdx :is(ul, ol) {
@apply space-y-2;
Expand Down

0 comments on commit b997a0f

Please sign in to comment.