Vlite - a lite demo server inspired by Vite
Tags: vite, vue, react, css
Published: 2024-03-07 03:12:56


![Vlite logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nulajmbj492gp2u1a726.png)

Today, I'd like to introduce [Vlite](https://github.com/jinjiang/vlite), a lite demo server inspired by Vite.

## Usage

With Vlite, you can **show your ideas in an instant way**.

To install Vlite:

```bash
npm install -g @jinjiang/vlite
# or
yarn global add @jinjiang/vlite
# or
pnpm add -g @jinjiang/vlite
```

Now you can prepare an `index.html` and further write your Vue, React, or vanilla code besides it. e.g.:

{% embed https://gist.github.com/Jinjiang/f4a919e62e6fa0f08d7dfc95e0614999.js %}

Then just run `vlite` on this directory, you will preview your app on your browser. _That's it. No npm installation, no build steps, just works like a static local server._

## The idea

It came out when I built some quick frontend demos. Usually there are 3 possible ways to do that:

1. use static server like `http-serve` or `serve`, with which you can write HTML, CSS, and JavaScript that browsers natively support.
2. use a local dev env like Vite, more or less you have to set up your local project and install dependencies via a package manager like npm.
3. use an online edit-n-preview platform like CodeSandbox or StackBlitz.

For me, none of them was straightforward enough. So I was wondering whether we could have a more instant way to achieve that. It was better to be local first, but not too many steps to get started.

At the same time, I'd like to put some extra considerations and trade-offs in:

### Target 1: ESM-friendly

Nowadays, all the browsers natively support ES Modules very well. That means you don't have to bundle your large project to run. **We can just treat every file as an ESM to response**.

For the possible upstream and downstream workflow, I think sticking on ESM is always a good idea. For example, I've noticed a new proposal from tc39 called ["JavaScript Module Declarations"](https://github.com/tc39/proposal-module-declarations) to inline your modules. Maybe one day we can further bundle your project in a simple action with this. That's why I'd like to make it as ESM-friendly as possible.

### Target 2: Simplifying the compilations

For local demos, I can see a lot of potentials to simplify the compilations.

1. Polyfills and syntax sugar compilations can be skipped for local demos.
2. I feel the frontend syntax choices are slightly converging. And their compilers are more and more mature. I decided to start from React and Vue projects, which include TS/JSX, CSS, and Vue SFC.
3. You can find faster and simpler options every day. They make the compilation for each file on the fly possible.
4. Removing all the optional processes like HMR, SSR, source maps, or any other DX enhancements.

It's just for local demos, right? So taking all above is not a big deal. On the other hand, **you might be surprised how simple it could be**. :wink:

## How it works?

Basically, the whole tool consists of 3 parts:

1. a http server to accept requests
2. a plugin system to manage the compilation process
3. a set of plugins to handle all kinds of files into ESMs

### A simple plugin system

The plugin system is heavily inspired by Vite/Rollup. I have to say their plugin system is SO good.

In Vlite, the plugin system only accepts 2 hooks (in pseudo code):

- `load(filepath): string | Buffer | undefined`: First of all, the default loader is always the plain file reader. And every plugin has a chance to overwrite the result by returning a string or a buffer
- `transform(filepath, queryParams, content): string | Buffer | undefined`: Every plugin has a chance to handle it. You can detect whether to handle or skip it by checking the file extension name or query params.

Comparing to Vite/Rollup:

- I removed the `resolveId()` hook since technically every request should correspond to a local file.
- I provided a default `load()` behavior to read content from file system. In all the cases so far it's enough. *The `load()` hook was only designed for future usage like virtual modules.*
- I didn't introduce source maps neither. So the input and output could be simple and straightforward.

### Compiler choices

![compiler choices of vlite](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83v6buwvz2yfgju3tu3j.png)

**For TS and JSX files**, I chose [sucrase](https://github.com/alangpierce/sucrase), the same choice as Vue Playground made. It's super fast and esay to config.

**For CSS files**, I converted each of them into ESM code with `document.createElement('style')` and `document.head.appendChild()`.

e.g. imaging a CSS file:

```css
body { margin: 0 }
```

it will be processed into:

```js
const style = document.createElement('style')
style.textContent = 'body { margin: 0 }'
document.head.appendChild(style)
```

- You might ask how to deal with CSS Modules and their generated class names. I used [postcss-modules](https://www.npmjs.com/package/postcss-modules) to convert it and export those class names as a JSON object.

e.g. a CSS Module file:

```css
.foo { color: red }
```

will be processed into:

```js
const style = document.createElement('style')
style.textContent = '.foo_xxxxxx { color: red }'
document.head.appendChild(style)
export default { foo: 'foo_xxxxxx' }
```

**For Vue SFCs**, I reused another package that I created: [vue-simple-compiler](https://github.com/Jinjiang/vue-simple-compiler). It can compile a Vue SFC into plain JavaScript and CSS imports.

**For assets like images**, it's a little bit tricky. Since there are 2 ways to consume them:

- request the file in browsers,
- import the file path in JavaScript.

So I introduced a special query parameter `?url`. If the request has this query parameter, the server will respond with the file path as the default export in ESM.

e.g. for an image `logo.png`:

- If you request it directly from the browser, you will get the image file in binary data.
- If you write `import logo from './logo.png'` in JS/TS, the import statement will be compiled into `import logo from './logo.png?url'`. Then the request `/logo.png?url` would return `export default '/logo.png'`. So when you further write code like `<img src={logo} />` it will work as well.

**For npm packages**, I used a ES imports parser [parse-imports](https://github.com/TomerAberbach/parse-imports) to find them, and replaced each of them with a [esm.sh](https://esm.sh/) URL. It's a fast CDN providing npm packages in ESM on the fly.

e.g. the import `import { createApp } from 'vue'` will be compiled into `import { createApp } from 'https://esm.sh/vue'`.

## Conclusions and future plans

That's all above [Vlite](https://github.com/Jinjiang/vlite). It's designed for quick demos with simplicity and straightforwardness. No extra stuff like HMR, SSR, source maps, etc.

To be honest, I still have some other ideas to put in, like:

- Pick more performant parsers and compilers.
- Support a few more frameworks, probably.
- SPA mode to serve HTML entry file in any case the request path doesn't match a file.
- Offline mode by caching certain packages from esm.sh
- Build mode to generate all the files into ESM for further static servers usage.
- A new "bundle" mode to bundle generated file into one piece by JavaScript Module Declarations

Anyway, just all for quick demos. And you are very welcome to give a try.

Cheers.