diff --git a/docs/guide/a-story.md b/docs/guide/a-story.md index d1ae4dde..5c391b47 100644 --- a/docs/guide/a-story.md +++ b/docs/guide/a-story.md @@ -1,15 +1,55 @@ -# Build your server +# A story ## The beginning -Create a `app.config.js` file in the root of your project and add the following: +In a new directory, create a `package.json` for your project, + +```json [package.json] +{ + "name": "my-app", + "type": "module", + "private": true, + "scripts": { + "dev": "vinxi dev", + "build": "vinxi build", + "start": "vinxi start" + }, + "dependencies": { + "vinxi": "0.3.8" + } +} +``` -```ts [app.config.js] +And run `npm install` to install the dependencies. + +```bash [npm] +npm install +``` + +Create a `app.config.ts` file in the root of your project and add the following: + +::: code-group + +```ts [app.config.ts] import { createApp } from "vinxi"; export default createApp(); ``` +::: + +You are ready to start your app in development mode now, + +```bash [npm] +npm run dev +``` + +::: warning + +You will see an error in the browser if you go to `http://localhost:3000/` right now. Your app doesn't have anything to show right now. Don't worry, we will get there. + +::: + ## A static file server A simple web server typically serves static files from a directory on disk. Let's add a `public` directory and serve it's contents: @@ -72,12 +112,14 @@ We need to add the script to the `index.html` file for the browser to actually f ``` -info +::: info Note how we can use `/app.js` to refer to the `/public/app.js` file from the `index.html` file. This is because we have set the `base` to `/` in the router config. This means that all the files in the `public` directory are served at the routes corresponding to their paths excluding the `public` part, `/public/app.js` is served at `/app.js`. If we had set the `base` to `/static`, then the `/public/app.js` file would be served at `/static/app.js`. But so would the `public/index.html`, and that would be a problem. We will deal with that problem later. But its usually a good idea to set the `base` to `/` for the `static` mode. so that people's expectations are met regarding the routes of the files in the `public` directory. +::: + You can now open the browser console and see the message. But still, there's nothing you can do on the app. Let's add a button. @@ -91,7 +133,7 @@ But still, there's nothing you can do on the app. Let's add a button.

Hello World

- + @@ -120,7 +162,7 @@ document.getElementById("my-button").addEventListener("click", () => {

Hello World

- + @@ -138,7 +180,7 @@ Okay, this is getting fun. Lets add some styles.

Hello World

- + @@ -161,7 +203,7 @@ Ahh, I don't like that background color. Needs more pop. Let's change it to a ni ```css [public/app.css] body { - background-color: #0000ff; + background-color: #000022; color: #fff; } ``` @@ -208,7 +250,7 @@ Let's refresh the page again (I know, I know, it's annoying). Click the button.

Hello World

- + @@ -229,7 +271,7 @@ Before we go down this path, I discovered some other problems faced here: - If I want to write typescript, I need to add a transpile step for the browser to understand it. - If I want to use React, Vue, etc. I need to add a transpile step for the browser to understand it. -This is just the tip of the iceberg for thr problems faced with working with bare HTML, CSS and JS. We want to write the code this way. But the browser only understands a certain way of doing things. We need to bridge the gap between the two. This is where Vite comes in. It's a tool that bridges the gap between the way we want to write code and the way the browser understands code. It does this by providing a development server runtime that transforms our code to a format the browser understands. It also provides a builder that transforms our code to a production ready format with a lot of optimizations. It also provides a plugin API that allows us to customize the development server and builder. +This is just the tip of the iceberg for thr problems faced with working with bare HTML, CSS and JS. We want to write the code this way. But the browser only understands a certain way of doing things. We need to bridge the gap between the two. This is where Vite comes in. It's a tool that bridges the gap between the way we want to write code and the way the browser understands code. It does this by providing a development server runtime that transforms our code to a format the browser understands. It also provides a builder that transforms our code to a production ready format with a lot of optimizations. And lasty, it provides a plugin API that allows us to customize the development server and builder. `vinxi` comes with a built-in Vite development server and builder. Let's use it. @@ -256,9 +298,23 @@ export default createApp({ }); ``` -We will move the files from the `public` directory to the root of the project. Let's run the dev server again. +We will move the files from the `public` directory to the root of the project. Refresh again now. + +Some of our problems have now been solved. Change something in the JavaScript file. The browser will automatically reload. Install a `npm` package and use it in your code. The browser will know what to do. Change to typescript, install React, Vue, etc. The browser will know what to do. Because vite tells it. + +For the CSS to be processed by vite, we need to import it in the javascript file. Let's do that. + +```ts [app.js] +import confetti from "canvas-confetti"; + +import "./app.css"; + +document.getElementById("my-button").addEventListener("click", () => { + confetti(); +}); +``` -All our problems have now been solved. Change something in the CSS. or the JavaScript file. The browser will automatically reload. Install a node-module and use it in your code. The browser will know what to do. Change to typescript, install React, Vue, etc. The browser will know what to do. Because vite tells it. +Now, changes in the CSS file will be instantly reflected in the browser without a refresh. Okay now that we all this power, lets thing bigger. What if we sent an email when the button is clicked. We can use the `nodemailer` package to do that. Let's install it. diff --git a/docs/guide/add-to-existing-vite-app.md b/docs/guide/add-to-existing-vite-app.md index 9ed6bdfb..0dde5259 100644 --- a/docs/guide/add-to-existing-vite-app.md +++ b/docs/guide/add-to-existing-vite-app.md @@ -1,3 +1,111 @@ # Add to existing Vite app -(Coming Soon) +While vite is amazing as a devkit for client-side web applications, it requires some setup to use the server in the same application. The funny part is that there is a server running both in dev and prod, its just that you don't get access to it. + +Vinxi gives you the ability to add server-side functionality (SSR) to an existing Vite app. + +A typical `vite` app is a single-page application, with an `index.html` file, a `public` directory and a `vite.config.ts` file. + +Lets install `vinxi` to get started: + +```bash [npm] +npm install vinxi +``` + +The first step is to make your `vite` app into a `vinxi` app. You don't need to create any new files. Vinxi works with `vite.config.ts` files too. You just need to change what you export from the file. + +Here is an example of a `vite.config.ts` file that exports the same `vite` app as a `vinxi` app: + +```ts twoslash +import { createApp } from "vinxi"; +import { config } from "vinxi/plugins/config"; + +export default createApp({ + routers: [ + { + name: "public", + type: "static", + dir: "./public", + }, + { + name: "client", + type: "spa", + handler: "./index.html", + base: "/", + plugins: () => [ + config("custom", { + // additional vite options + }), + // additional vite plugins + ], + }, + ], +}); +``` + +Now running `npm run dev` will start the Vinxi dev server and you will get the same experience as you did with `vite`. + +The only difference is that you can now add server-side functionality to your app, and much more. Let's see how. + +Let's imagine we need to send an email when a user submits a form. We can use the `nodemailer` package to do that. Let's install it. + +```bash[npm] +npm install nodemailer +``` + +```bash[yarn] +yarn add nodemailer +``` + +```bash[pnpm] +pnpm add nodemailer +``` + +But `nodemailer` is not something you can use in the browser. You need to run it in `node` (on the server). So we need some kind of API routes. In `vinxi`, you can add a server handler for an API to your app. + +```ts +import { createApp } from "vinxi"; + +export default createApp({ + routers: [ + { + name: "public", + type: "static", + dir: "./public", + }, + { + name: "client", + type: "spa", + handler: "./index.html", + base: "/", + plugins: () => [ + config("custom", { + // additional vite options + }), + // additional vite plugins + ], + }, + { + name: "api", + type: "http", + handler: "./api.ts", + base: "/api", + }, + ], +}); +``` + +```ts [api.ts] +import nodemailer from "nodemailer"; +import { eventHandler } from "vinxi/http"; + +export default eventHandler(async (event) => { + await nodemailer.sendMail({ + from: "", + }); + + return "done"; +}); +``` + +You can now hit `http://localhost:3000/api` from your frontend to send an email. diff --git a/docs/guide/create-your-first-app.md b/docs/guide/create-your-first-app.md index aa52b154..b4c1ffeb 100644 --- a/docs/guide/create-your-first-app.md +++ b/docs/guide/create-your-first-app.md @@ -1,5 +1,7 @@ # Create your first app +Vinxi helps you build the full spectrum of applications with javascript, be it a static site, SPA, just an API or a fully featured SSR'ed and hydrated application. + ### React SSR ```ts diff --git a/packages/vinxi/bin/cli.mjs b/packages/vinxi/bin/cli.mjs index b228280e..da7499cb 100755 --- a/packages/vinxi/bin/cli.mjs +++ b/packages/vinxi/bin/cli.mjs @@ -95,9 +95,13 @@ const command = defineCommand({ watcher.on("all", async (ctx, path) => { log(c.dim(c.green("change detected in " + path))); log(c.dim(c.green("reloading app"))); - const newApp = await loadApp(configFile, args); - if (!newApp) return; - restartDevServer(newApp); + try { + const newApp = await loadApp(configFile, args); + if (!newApp) return; + restartDevServer(newApp); + } catch (e) { + console.error(e) + } }); } async function createKeypressWatcher() { @@ -155,12 +159,16 @@ const command = defineCommand({ fsWatcher.on("all", async (path) => { log(c.dim(c.green("change detected in " + path))); log(c.dim(c.green("reloading app"))); - const newApp = await loadApp(configFile, args); - if (!newApp) return; + try { + const newApp = await loadApp(configFile, args); + if (!newApp) return; - fsWatcher.close(); - createWatcher(); - restartDevServer(newApp); + fsWatcher.close(); + createWatcher(); + restartDevServer(newApp); + } catch (e) { + console.error(e) + } }); return; }