From d45c4b164f8f93e481aa71b1e2cbbe9edbfaa07b Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Sun, 19 Jun 2022 17:00:05 -0300
Subject: [PATCH 01/14] rm old package
---
packages/fastify-dx-vue/index.js | 0
packages/fastify-dx-vue/package.json | 6 ------
2 files changed, 6 deletions(-)
delete mode 100644 packages/fastify-dx-vue/index.js
delete mode 100644 packages/fastify-dx-vue/package.json
diff --git a/packages/fastify-dx-vue/index.js b/packages/fastify-dx-vue/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/packages/fastify-dx-vue/package.json b/packages/fastify-dx-vue/package.json
deleted file mode 100644
index 586576e..0000000
--- a/packages/fastify-dx-vue/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "fastify-dx-vue",
- "version": "0.0.0",
- "files": ["index.js"],
- "license": "MIT"
-}
\ No newline at end of file
From a40b2f2e1eaa016f64e3c727bcab2e445b407e92 Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Sun, 19 Jun 2022 17:03:36 -0300
Subject: [PATCH 02/14] start from react
---
packages/fastify-dx-vue/.eslintrc | 32 ++++
packages/fastify-dx-vue/README.md | 90 +++++++++
packages/fastify-dx-vue/index.js | 177 ++++++++++++++++++
packages/fastify-dx-vue/package.json | 48 +++++
packages/fastify-dx-vue/plugin.cjs | 107 +++++++++++
packages/fastify-dx-vue/server/context.js | 65 +++++++
packages/fastify-dx-vue/server/stream.js | 53 ++++++
packages/fastify-dx-vue/virtual/context.js | 4 +
packages/fastify-dx-vue/virtual/core.jsx | 140 ++++++++++++++
packages/fastify-dx-vue/virtual/create.jsx | 7 +
packages/fastify-dx-vue/virtual/layouts.js | 14 ++
.../virtual/layouts/default.jsx | 12 ++
packages/fastify-dx-vue/virtual/mount.js | 47 +++++
packages/fastify-dx-vue/virtual/resource.js | 68 +++++++
packages/fastify-dx-vue/virtual/root.jsx | 10 +
packages/fastify-dx-vue/virtual/routes.js | 121 ++++++++++++
starters/vue/.eslintignore | 1 +
starters/vue/.eslintrc | 31 +++
starters/vue/client/assets/logo.svg | 84 +++++++++
starters/vue/client/base.css | 56 ++++++
starters/vue/client/context.js | 36 ++++
starters/vue/client/index.html | 13 ++
starters/vue/client/index.js | 8 +
starters/vue/client/layouts/auth.jsx | 25 +++
starters/vue/client/layouts/default.jsx | 9 +
starters/vue/client/pages/client-only.jsx | 23 +++
starters/vue/client/pages/index.jsx | 35 ++++
starters/vue/client/pages/server-only.jsx | 17 ++
starters/vue/client/pages/streaming.jsx | 46 +++++
starters/vue/client/pages/using-auth.jsx | 39 ++++
starters/vue/client/pages/using-data.jsx | 46 +++++
starters/vue/client/pages/using-store.jsx | 36 ++++
starters/vue/client/root.jsx | 11 ++
starters/vue/package.json | 45 +++++
starters/vue/postcss.config.cjs | 9 +
starters/vue/server.js | 32 ++++
starters/vue/vite.config.js | 29 +++
37 files changed, 1626 insertions(+)
create mode 100644 packages/fastify-dx-vue/.eslintrc
create mode 100644 packages/fastify-dx-vue/README.md
create mode 100644 packages/fastify-dx-vue/index.js
create mode 100644 packages/fastify-dx-vue/package.json
create mode 100644 packages/fastify-dx-vue/plugin.cjs
create mode 100644 packages/fastify-dx-vue/server/context.js
create mode 100644 packages/fastify-dx-vue/server/stream.js
create mode 100644 packages/fastify-dx-vue/virtual/context.js
create mode 100644 packages/fastify-dx-vue/virtual/core.jsx
create mode 100644 packages/fastify-dx-vue/virtual/create.jsx
create mode 100644 packages/fastify-dx-vue/virtual/layouts.js
create mode 100644 packages/fastify-dx-vue/virtual/layouts/default.jsx
create mode 100644 packages/fastify-dx-vue/virtual/mount.js
create mode 100644 packages/fastify-dx-vue/virtual/resource.js
create mode 100644 packages/fastify-dx-vue/virtual/root.jsx
create mode 100644 packages/fastify-dx-vue/virtual/routes.js
create mode 100644 starters/vue/.eslintignore
create mode 100644 starters/vue/.eslintrc
create mode 100644 starters/vue/client/assets/logo.svg
create mode 100644 starters/vue/client/base.css
create mode 100644 starters/vue/client/context.js
create mode 100644 starters/vue/client/index.html
create mode 100644 starters/vue/client/index.js
create mode 100644 starters/vue/client/layouts/auth.jsx
create mode 100644 starters/vue/client/layouts/default.jsx
create mode 100644 starters/vue/client/pages/client-only.jsx
create mode 100644 starters/vue/client/pages/index.jsx
create mode 100644 starters/vue/client/pages/server-only.jsx
create mode 100644 starters/vue/client/pages/streaming.jsx
create mode 100644 starters/vue/client/pages/using-auth.jsx
create mode 100644 starters/vue/client/pages/using-data.jsx
create mode 100644 starters/vue/client/pages/using-store.jsx
create mode 100644 starters/vue/client/root.jsx
create mode 100644 starters/vue/package.json
create mode 100644 starters/vue/postcss.config.cjs
create mode 100644 starters/vue/server.js
create mode 100644 starters/vue/vite.config.js
diff --git a/packages/fastify-dx-vue/.eslintrc b/packages/fastify-dx-vue/.eslintrc
new file mode 100644
index 0000000..2ade438
--- /dev/null
+++ b/packages/fastify-dx-vue/.eslintrc
@@ -0,0 +1,32 @@
+{
+ parser: '@babel/eslint-parser',
+ parserOptions: {
+ requireConfigFile: false,
+ ecmaVersion: 2021,
+ sourceType: 'module',
+ babelOptions: {
+ presets: ['@babel/preset-react'],
+ },
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ extends: [
+ 'plugin:react/recommended',
+ 'standard',
+ ],
+ plugins: [
+ 'react',
+ ],
+ rules: {
+ 'react/prop-types': 'off',
+ 'react/react-in-jsx-scope': 'off',
+ 'comma-dangle': ['error', 'always-multiline'],
+ 'import/no-absolute-path': 'off',
+ },
+ settings: {
+ react: {
+ version: '18.0',
+ },
+ },
+}
diff --git a/packages/fastify-dx-vue/README.md b/packages/fastify-dx-vue/README.md
new file mode 100644
index 0000000..8ae8e6f
--- /dev/null
+++ b/packages/fastify-dx-vue/README.md
@@ -0,0 +1,90 @@
+# fastify-dx-react [![NPM version](https://img.shields.io/npm/v/fastify-dx-react.svg?style=flat)](https://www.npmjs.com/package/fastify-dx-react) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
+
+- [**Introduction**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-react/README.md#introduction)
+- [**Quick Start**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-react/README.md#quick-start)
+- [**Package Scripts**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-react/README.md#package-scripts)
+- [**Basic Setup**](https://github.com/fastify/fastify-dx/blob/main/docs/react/basic-setup.md)
+- [**Project Structure**](https://github.com/fastify/fastify-dx/blob/main/docs/react/project-structure.md)
+- [**Rendering Modes**](https://github.com/fastify/fastify-dx/blob/main/docs/react/rendering-modes.md)
+- [**Routing Configuration**](https://github.com/fastify/fastify-dx/blob/main/docs/react/routing-config.md)
+- [**Data Prefetching**](https://github.com/fastify/fastify-dx/blob/main/docs/react/data-prefetching.md)
+- [**Route Layouts**](https://github.com/fastify/fastify-dx/blob/main/docs/react/route-layouts.md)
+- [**Route Context**](https://github.com/fastify/fastify-dx/blob/main/docs/react/route-context.md)
+- [**Route Enter Event**](https://github.com/fastify/fastify-dx/blob/main/docs/react/route-enter.md)
+- [**Virtual Modules**](https://github.com/fastify/fastify-dx/blob/main/docs/react/virtual-modules.md)
+
+## Introduction
+
+**Fastify DX for React** is a renderer adapter for [**fastify-vite**](https://github.com/fastify/fastify-vite).
+
+It is a **fast**, **lightweight** alternative to Next.js and Remix packed with **Developer Experience** features.
+
+It has an extremely small core (~1k LOC total) and is built on top of [Fastify](https://github.com/fastify/fastify), [Vite](https://vitejs.dev/), [React Router](https://reactrouter.com/docs/en/v6) and [Valtio](https://github.com/pmndrs/valtio).
+
+[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/v0.0.1).
+
+> At this stage this project is mostly a [**one-man show**](https://github.com/sponsors/galvez), who's devoting all his free time to its completion. Contributions are extremely welcome, as well as bug reports for any issues you may find.
+
+In this first alpha release it's still missing a test suite. The same is true for [**fastify-vite**]().
+
+It'll move into **beta** status when test suites are added to both packages.
+
+## Quick Start
+
+Ensure you have **Node v16+**.
+
+Make a copy of [**starters/react**](https://github.com/fastify/fastify-dx/tree/dev/starters/react). If you have [`degit`](https://github.com/Rich-Harris/degit), run the following from a new directory:
+
+```bash
+degit fastify/fastify-dx/starters/react
+```
+
+> **If you're starting a project from scratch**, you'll need these packages installed.
+>
+> ```bash
+> npm i fastify fastify-vite fastify-dx-react -P
+> npm i @vitejs/plugin-react -D
+> ```
+
+
+Run `npm install`.
+
+Run `npm run dev`.
+
+Visit `http://localhost:3000/`.
+
+## What's Included
+
+That will get you a **starter template** with:
+
+- A minimal [Fastify](https://github.com/fastify/fastify) server.
+- Some dummy API routes.
+- A `pages/` folder with some [demo routes](https://github.com/fastify/fastify-dx/tree/dev/starters/react/client/pages).
+- All configuration files.
+
+It also includes some _**opinionated**_ essentials:
+
+- [**PostCSS Preset Env**](https://www.npmjs.com/package/postcss-preset-env) by [**Jonathan Neal**](https://github.com/jonathantneal), which enables [several modern CSS features](https://preset-env.cssdb.org/), such as [**CSS Nesting**](https://www.w3.org/TR/css-nesting-1/).
+
+- [**UnoCSS**](https://github.com/unocss/unocss) by [**Anthony Fu**](https://antfu.me/), which supports all [Tailwind utilities](https://uno.antfu.me/) and many other goodies through its [default preset](https://github.com/unocss/unocss/tree/main/packages/preset-uno).
+
+- [**Valtio**](https://github.com/pmndrs/valtio) by [**Daishi Kato**](https://blog.axlight.com/), with a global and SSR-ready store which you can use anywhere.
+
+
+## Package Scripts
+
+`npm run dev` boots the development server.
+
+`npm run build` creates the production bundle.
+
+`npm run serve` serves the production bundle.
+
+## Meta
+
+Created by [Jonas Galvez](https://github.com/sponsors/galvez), **Engineering Manager** and **Open Sourcerer** at [NearForm](https://nearform.com).
+
+## Sponsors
+
+
+
+Also [**Duc-Thien Bui**](https://github.com/aecea) and [**Tom Preston-Werner**](https://github.com/mojombo) [via GitHub Sponsors](https://github.com/sponsors/galvez). _Thank you!_
diff --git a/packages/fastify-dx-vue/index.js b/packages/fastify-dx-vue/index.js
new file mode 100644
index 0000000..e6673cc
--- /dev/null
+++ b/packages/fastify-dx-vue/index.js
@@ -0,0 +1,177 @@
+// Used to send a readable stream to reply.send()
+import { Readable } from 'stream'
+
+// fastify-vite's minimal HTML templating function,
+// which extracts interpolation variables from comments
+// and returns a function with the generated code
+import { createHtmlTemplateFunction } from 'fastify-vite'
+
+// Used to safely serialize JavaScript into
+// '
+ )
+ }
+ // Render page-level elements
+ const head = new Head(context.head).render()
+ // Create readable stream with prepended and appended chunks
+ const readable = Readable.from(generateHtmlStream({
+ body: body && (
+ context.streaming
+ ? onShellReady(body)
+ : onAllReady(body)
+ ),
+ head: headTemplate({ ...context, head, hydration }),
+ footer: footerTemplate(context),
+ }))
+ // Send out header and readable stream with full response
+ this.type('text/html')
+ this.send(readable)
+ }
+}
+
+export async function createRenderFunction ({ routes, create }) {
+ // create is exported by client/index.js
+ return function (req) {
+ // Create convenience-access routeMap
+ const routeMap = Object.fromEntries(routes.toJSON().map((route) => {
+ return [route.path, route]
+ }))
+ // Creates main React component with all the SSR context it needs
+ const app = !req.route.clientOnly && create({
+ routes,
+ routeMap,
+ ctxHydration: req.route,
+ url: req.url,
+ })
+ // Perform SSR, i.e., turn app.instance into an HTML fragment
+ // The SSR context data is passed along so it can be inlined for hydration
+ return { routes, context: req.route, body: app }
+ }
+}
+
+export function createRouteHandler (client, scope, config) {
+ return function (req, reply) {
+ reply.html(reply.render(req))
+ return reply
+ }
+}
+
+export function createRoute ({ client, handler, errorHandler, route }, scope, config) {
+ const onRequest = async function onRequest (req, reply) {
+ req.route = await RouteContext.create(
+ scope,
+ req,
+ reply,
+ route,
+ client.context,
+ )
+ }
+ if (route.getData) {
+ // If getData is provided, register JSON endpoint for it
+ scope.get(`/-/data${route.path}`, {
+ onRequest,
+ async handler (req, reply) {
+ reply.send(await route.getData(req.route))
+ },
+ })
+ }
+
+ // See https://github.com/fastify/fastify-dx/blob/main/URMA.md
+ const hasURMAHooks = Boolean(
+ route.getData || route.getMeta || route.onEnter,
+ )
+
+ // Extend with route context initialization module
+ RouteContext.extend(client.context)
+
+ scope.get(route.path, {
+ onRequest,
+ // If either getData or onEnter are provided,
+ // make sure they run before the SSR route handler
+ ...hasURMAHooks && {
+ async preHandler (req, reply) {
+ try {
+ if (route.getData) {
+ req.route.data = await route.getData(req.route)
+ }
+ if (route.getMeta) {
+ req.route.head = await route.getMeta(req.route)
+ }
+ if (route.onEnter) {
+ if (!req.route.data) {
+ req.route.data = {}
+ }
+ const result = await route.onEnter(req.route)
+ Object.assign(req.route.data, result)
+ }
+ } catch (err) {
+ if (config.dev) {
+ console.error(err)
+ }
+ req.route.error = err
+ }
+ },
+ },
+ handler,
+ errorHandler,
+ ...route,
+ })
+}
diff --git a/packages/fastify-dx-vue/package.json b/packages/fastify-dx-vue/package.json
new file mode 100644
index 0000000..9b5f427
--- /dev/null
+++ b/packages/fastify-dx-vue/package.json
@@ -0,0 +1,48 @@
+{
+ "scripts": {
+ "lint": "eslint . --ext .js,.jsx --fix"
+ },
+ "type": "module",
+ "main": "index.js",
+ "name": "fastify-dx-react",
+ "version": "0.0.2",
+ "files": [
+ "virtual/create.jsx",
+ "virtual/root.jsx",
+ "virtual/layouts.js",
+ "virtual/layouts/default.jsx",
+ "virtual/context.js",
+ "virtual/mount.js",
+ "virtual/resource.js",
+ "virtual/core.jsx",
+ "virtual/routes.js",
+ "index.js",
+ "plugin.cjs",
+ "server/context.js",
+ "server/stream.js"
+ ],
+ "license": "MIT",
+ "exports": {
+ ".": "./index.js",
+ "./plugin": "./plugin.cjs"
+ },
+ "dependencies": {
+ "react": "^18.1.0",
+ "react-dom": "^18.1.0",
+ "react-router-dom": "^6.3.0",
+ "devalue": "^2.0.1",
+ "unihead": "^0.0.6",
+ "valtio": "^1.6.1"
+ },
+ "devDependencies": {
+ "@babel/eslint-parser": "^7.16.0",
+ "@babel/preset-react": "^7.16.0",
+ "@vitejs/plugin-react": "^1.3.2",
+ "eslint": "^7.32.0",
+ "eslint-config-standard": "^16.0.2",
+ "eslint-plugin-import": "^2.22.1",
+ "eslint-plugin-node": "^11.1.0",
+ "eslint-plugin-promise": "^4.3.1",
+ "eslint-plugin-react": "^7.29.4"
+ }
+}
\ No newline at end of file
diff --git a/packages/fastify-dx-vue/plugin.cjs b/packages/fastify-dx-vue/plugin.cjs
new file mode 100644
index 0000000..2ec2063
--- /dev/null
+++ b/packages/fastify-dx-vue/plugin.cjs
@@ -0,0 +1,107 @@
+const { readFileSync, existsSync } = require('fs')
+const { dirname, join, resolve } = require('path')
+const { fileURLToPath } = require('url')
+
+function viteReactFastifyDX (config = {}) {
+ const prefix = /^\/?dx:/
+ const routing = Object.assign({
+ globPattern: '/pages/**/*.jsx',
+ paramPattern: /\[(\w+)\]/,
+ }, config)
+ const virtualRoot = resolve(__dirname, 'virtual')
+ const virtualModules = [
+ 'mount.js',
+ 'resource.js',
+ 'routes.js',
+ 'layouts.js',
+ 'create.jsx',
+ 'root.jsx',
+ 'layouts/',
+ 'context.js',
+ 'core.jsx'
+ ]
+ virtualModules.includes = function (virtual) {
+ if (!virtual) {
+ return false
+ }
+ for (const entry of this) {
+ if (virtual.startsWith(entry)) {
+ return true
+ }
+ }
+ return false
+ }
+ const virtualModuleInserts = {
+ 'routes.js': {
+ $globPattern: routing.globPattern,
+ $paramPattern: routing.paramPattern,
+ }
+ }
+
+ let viteProjectRoot
+
+ function loadVirtualModuleOverride (virtual) {
+ if (!virtualModules.includes(virtual)) {
+ return
+ }
+ const overridePath = resolve(viteProjectRoot, virtual)
+ if (existsSync(overridePath)) {
+ return overridePath
+ }
+ }
+
+ function loadVirtualModule (virtual) {
+ if (!virtualModules.includes(virtual)) {
+ return
+ }
+ let code = readFileSync(resolve(virtualRoot, virtual), 'utf8')
+ if (virtualModuleInserts[virtual]) {
+ for (const [key, value] of Object.entries(virtualModuleInserts[virtual])) {
+ code = code.replace(new RegExp(escapeRegExp(key), 'g'), value)
+ }
+ }
+ return {
+ code,
+ map: null,
+ }
+ }
+
+ // Thanks to https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js
+ function escapeRegExp (s) {
+ return s
+ .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
+ .replace(/-/g, '\\x2d')
+ }
+
+ return {
+ name: 'vite-plugin-react-fastify-dx',
+ config (config, { command }) {
+ if (command === 'build' && config.build?.ssr) {
+ config.build.rollupOptions = {
+ output: {
+ format: 'es',
+ },
+ }
+ }
+ },
+ configResolved (config) {
+ viteProjectRoot = config.root
+ },
+ async resolveId (id) {
+ const [, virtual] = id.split(prefix)
+ if (virtual) {
+ const override = await loadVirtualModuleOverride(virtual)
+ if (override) {
+ return override
+ }
+ return id
+ }
+ },
+ load (id) {
+ const [, virtual] = id.split(prefix)
+ return loadVirtualModule(virtual)
+ },
+ }
+}
+
+module.exports = viteReactFastifyDX
diff --git a/packages/fastify-dx-vue/server/context.js b/packages/fastify-dx-vue/server/context.js
new file mode 100644
index 0000000..fab9a2e
--- /dev/null
+++ b/packages/fastify-dx-vue/server/context.js
@@ -0,0 +1,65 @@
+
+const routeContextInspect = Symbol.for('nodejs.util.inspect.custom')
+
+export default class RouteContext {
+ static async create (server, req, reply, route, contextInit) {
+ const routeContext = new RouteContext(server, req, reply, route)
+ if (contextInit) {
+ if (contextInit.state) {
+ routeContext.state = contextInit.state()
+ }
+ if (contextInit.default) {
+ await contextInit.default(routeContext)
+ }
+ }
+ return routeContext
+ }
+
+ constructor (server, req, reply, route) {
+ this.server = server
+ this.req = req
+ this.reply = reply
+ this.head = {}
+ this.state = null
+ this.data = route.data
+ this.firstRender = true
+ this.layout = route.layout
+ this.getMeta = !!route.getMeta
+ this.getData = !!route.getData
+ this.onEnter = !!route.onEnter
+ this.streaming = route.streaming
+ this.clientOnly = route.clientOnly
+ this.serverOnly = route.serverOnly
+ }
+
+ [routeContextInspect] () {
+ return {
+ ...this,
+ server: { [routeContextInspect]: () => '[Server]' },
+ req: { [routeContextInspect]: () => '[Request]' },
+ reply: { [routeContextInspect]: () => '[Reply]' },
+ }
+ }
+
+ toJSON () {
+ return {
+ state: this.state,
+ data: this.data,
+ layout: this.layout,
+ getMeta: this.getMeta,
+ getData: this.getData,
+ onEnter: this.onEnter,
+ firstRender: this.firstRender,
+ clientOnly: this.clientOnly,
+ }
+ }
+}
+
+RouteContext.extend = function (initial) {
+ const { default: _, ...extra } = initial
+ for (const [prop, value] of Object.entries(extra)) {
+ if (prop !== 'data' && prop !== 'state') {
+ Object.defineProperty(RouteContext.prototype, prop, value)
+ }
+ }
+}
diff --git a/packages/fastify-dx-vue/server/stream.js b/packages/fastify-dx-vue/server/stream.js
new file mode 100644
index 0000000..ea4f94e
--- /dev/null
+++ b/packages/fastify-dx-vue/server/stream.js
@@ -0,0 +1,53 @@
+
+// Helper from the Node.js stream library to
+// make it easier to work with renderToPipeableStream()
+import { PassThrough } from 'stream'
+
+// React 18's preferred server-side rendering function,
+// which enables the combination of React.lazy() and Suspense
+import { renderToPipeableStream } from 'react-dom/server'
+
+// Helper function to prepend and append chunks the body stream
+export async function * generateHtmlStream ({ head, body, footer }) {
+ yield head
+ if (body) {
+ for await (const chunk of await body) {
+ yield chunk
+ }
+ }
+ yield footer
+}
+
+// Helper function to get an AsyncIterable (via PassThrough)
+// from the renderToPipeableStream() onShellReady event
+export function onShellReady (app) {
+ const duplex = new PassThrough()
+ return new Promise((resolve, reject) => {
+ try {
+ const pipeable = renderToPipeableStream(app, {
+ onShellReady () {
+ resolve(pipeable.pipe(duplex))
+ },
+ })
+ } catch (error) {
+ resolve(error)
+ }
+ })
+}
+
+// Helper function to get an AsyncIterable (via PassThrough)
+// from the renderToPipeableStream() onAllReady event
+export function onAllReady (app) {
+ const duplex = new PassThrough()
+ return new Promise((resolve, reject) => {
+ try {
+ const pipeable = renderToPipeableStream(app, {
+ onAllReady () {
+ resolve(pipeable.pipe(duplex))
+ },
+ })
+ } catch (error) {
+ resolve(error)
+ }
+ })
+}
diff --git a/packages/fastify-dx-vue/virtual/context.js b/packages/fastify-dx-vue/virtual/context.js
new file mode 100644
index 0000000..1e605f5
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/context.js
@@ -0,0 +1,4 @@
+// This file serves as a placeholder
+// if no context.js file is provided
+
+export default () => {}
diff --git a/packages/fastify-dx-vue/virtual/core.jsx b/packages/fastify-dx-vue/virtual/core.jsx
new file mode 100644
index 0000000..c930a49
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/core.jsx
@@ -0,0 +1,140 @@
+import { createContext, useContext, useEffect } from 'react'
+import { useLocation, BrowserRouter, Routes, Route } from 'react-router-dom'
+import { StaticRouter } from 'react-router-dom/server.mjs'
+import { createPath } from 'history'
+import { proxy, useSnapshot } from 'valtio'
+import { waitResource, waitFetch } from '/dx:resource.js'
+import layouts from '/dx:layouts.js'
+
+const isServer = typeof process === 'object'
+
+export const Router = isServer ? StaticRouter : BrowserRouter
+export const RouteContext = createContext({})
+
+export function useRouteContext () {
+ const routeContext = useContext(RouteContext)
+ if (routeContext.state) {
+ routeContext.snapshot = useSnapshot(routeContext.state)
+ }
+ return routeContext
+}
+
+export function DXApp ({
+ url,
+ routes,
+ head,
+ routeMap,
+ ctxHydration,
+}) {
+ return (
+
+ {
+ routes.map(({ path, component: Component }) =>
+
+
+
+ } />,
+ )
+ }
+
+ )
+}
+
+export function DXRoute ({ head, ctxHydration, ctx, children }) {
+ // If running on the server, assume all data
+ // functions have already ran through the preHandler hook
+ if (isServer) {
+ const Layout = layouts[ctxHydration.layout ?? 'default']
+ return (
+
+
+ {children}
+
+
+ )
+ }
+ // Note that on the client, window.route === ctxHydration
+
+ // Indicates whether or not this is a first render on the client
+ ctx.firstRender = window.route.firstRender
+
+ // If running on the client, the server context data
+ // is still available, hydrated from window.route
+ if (ctx.firstRender) {
+ ctx.data = window.route.data
+ ctx.head = window.route.head
+ }
+
+ const location = useLocation()
+ const path = createPath(location)
+
+ // When the next route renders client-side,
+ // force it to execute all URMA hooks again
+ useEffect(() => {
+ window.route.firstRender = false
+ }, [location])
+
+ // If we have a getData function registered for this route
+ if (!ctx.data && ctx.getData) {
+ try {
+ const { pathname, search } = location
+ // If not, fetch data from the JSON endpoint
+ ctx.data = waitFetch(`${pathname}${search}`)
+ } catch (status) {
+ // If it's an actual error...
+ if (status instanceof Error) {
+ ctx.error = status
+ }
+ // If it's just a promise (suspended state)
+ throw status
+ }
+ }
+
+ // Note that ctx.loader() at this point will resolve the
+ // memoized module, so there's barely any overhead
+
+ if (!ctx.firstRender && ctx.getMeta) {
+ const updateMeta = async () => {
+ const { getMeta } = await ctx.loader()
+ head.update(await getMeta(ctx))
+ }
+ waitResource(path, 'updateMeta', updateMeta)
+ }
+
+ if (!ctx.firstRender && ctx.onEnter) {
+ const runOnEnter = async () => {
+ const { onEnter } = await ctx.loader()
+ const updatedData = await onEnter(ctx)
+ if (!ctx.data) {
+ ctx.data = {}
+ }
+ Object.assign(ctx.data, updatedData)
+ }
+ waitResource(path, 'onEnter', runOnEnter)
+ }
+
+ const Layout = layouts[ctx.layout ?? 'default']
+
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/packages/fastify-dx-vue/virtual/create.jsx b/packages/fastify-dx-vue/virtual/create.jsx
new file mode 100644
index 0000000..cf222b2
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/create.jsx
@@ -0,0 +1,7 @@
+import Root from '/dx:root.jsx'
+
+export default function create ({ url, ...serverInit }) {
+ return (
+
+ )
+}
diff --git a/packages/fastify-dx-vue/virtual/layouts.js b/packages/fastify-dx-vue/virtual/layouts.js
new file mode 100644
index 0000000..3a065ab
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/layouts.js
@@ -0,0 +1,14 @@
+import { lazy } from 'react'
+
+const DefaultLayout = () => import('/dx:layouts/default.jsx')
+
+const appLayouts = import.meta.glob('/layouts/*.jsx')
+
+appLayouts['/layouts/default.jsx'] ??= DefaultLayout
+
+export default Object.fromEntries(
+ Object.keys(appLayouts).map((path) => {
+ const name = path.slice(9, -4)
+ return [name, lazy(appLayouts[path])]
+ }),
+)
diff --git a/packages/fastify-dx-vue/virtual/layouts/default.jsx b/packages/fastify-dx-vue/virtual/layouts/default.jsx
new file mode 100644
index 0000000..aecd273
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/layouts/default.jsx
@@ -0,0 +1,12 @@
+// This file serves as a placeholder
+// if no layout.jsx file is provided
+
+import { Suspense } from 'react'
+
+export default function Layout ({ children }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/packages/fastify-dx-vue/virtual/mount.js b/packages/fastify-dx-vue/virtual/mount.js
new file mode 100644
index 0000000..a7c10d8
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/mount.js
@@ -0,0 +1,47 @@
+import Head from 'unihead/client'
+import { createRoot, hydrateRoot } from 'react-dom/client'
+
+import create from '/dx:create.jsx'
+import routesPromise from '/dx:routes.js'
+
+mount('main')
+
+async function mount (target) {
+ if (typeof target === 'string') {
+ target = document.querySelector(target)
+ }
+ const context = await import('/dx:context.js')
+ const ctxHydration = await extendContext(window.route, context)
+ const head = new Head(window.route.head, window.document)
+ const resolvedRoutes = await routesPromise
+ const routeMap = Object.fromEntries(
+ resolvedRoutes.map((route) => [route.path, route]),
+ )
+
+ const app = create({
+ head,
+ ctxHydration,
+ routes: window.routes,
+ routeMap,
+ })
+ if (ctxHydration.clientOnly) {
+ createRoot(target).render(app)
+ } else {
+ hydrateRoot(target, app)
+ }
+}
+
+async function extendContext (ctx, {
+ // The route context initialization function
+ default: setter,
+ // We destructure state here just to discard it from extra
+ state,
+ // Other named exports from context.js
+ ...extra
+}) {
+ Object.assign(ctx, extra)
+ if (setter) {
+ await setter(ctx)
+ }
+ return ctx
+}
diff --git a/packages/fastify-dx-vue/virtual/resource.js b/packages/fastify-dx-vue/virtual/resource.js
new file mode 100644
index 0000000..69d7310
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/resource.js
@@ -0,0 +1,68 @@
+
+const fetchMap = new Map()
+const resourceMap = new Map()
+
+export function waitResource (path, id, promise) {
+ const resourceId = `${path}:${id}`
+ const loader = resourceMap.get(resourceId)
+ if (loader) {
+ if (loader.error) {
+ throw loader.error
+ }
+ if (loader.suspended) {
+ throw loader.promise
+ }
+ resourceMap.delete(resourceId)
+
+ return loader.result
+ } else {
+ const loader = {
+ suspended: true,
+ error: null,
+ result: null,
+ promise: null,
+ }
+ loader.promise = promise()
+ .then((result) => { loader.result = result })
+ .catch((loaderError) => { loader.error = loaderError })
+ .finally(() => { loader.suspended = false })
+
+ resourceMap.set(resourceId, loader)
+
+ return waitResource(path, id)
+ }
+}
+
+export function waitFetch (path) {
+ const loader = fetchMap.get(path)
+ if (loader) {
+ if (loader.error || loader.data?.statusCode === 500) {
+ if (loader.data?.statusCode === 500) {
+ throw new Error(loader.data.message)
+ }
+ throw loader.error
+ }
+ if (loader.suspended) {
+ throw loader.promise
+ }
+ fetchMap.delete(path)
+
+ return loader.data
+ } else {
+ const loader = {
+ suspended: true,
+ error: null,
+ data: null,
+ promise: null,
+ }
+ loader.promise = fetch(`/-/data${path}`)
+ .then((response) => response.json())
+ .then((loaderData) => { loader.data = loaderData })
+ .catch((loaderError) => { loader.error = loaderError })
+ .finally(() => { loader.suspended = false })
+
+ fetchMap.set(path, loader)
+
+ return waitFetch(path)
+ }
+}
diff --git a/packages/fastify-dx-vue/virtual/root.jsx b/packages/fastify-dx-vue/virtual/root.jsx
new file mode 100644
index 0000000..f3e51a3
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/root.jsx
@@ -0,0 +1,10 @@
+import { Suspense } from 'react'
+import { DXApp } from '/dx:core.jsx'
+
+export default function Root ({ url, serverInit }) {
+ return (
+
+
+
+ )
+}
diff --git a/packages/fastify-dx-vue/virtual/routes.js b/packages/fastify-dx-vue/virtual/routes.js
new file mode 100644
index 0000000..4fc95a0
--- /dev/null
+++ b/packages/fastify-dx-vue/virtual/routes.js
@@ -0,0 +1,121 @@
+/* global $paramPattern */
+
+import { lazy } from 'react'
+
+export default import.meta.env.SSR
+ ? createRoutes(import.meta.globEager('$globPattern'))
+ : hydrateRoutes(import.meta.glob('$globPattern'))
+
+async function createRoutes (from, { param } = { param: $paramPattern }) {
+ // Otherwise we get a ReferenceError, but since
+ // this function is only ran once, there's no overhead
+ class Routes extends Array {
+ toJSON () {
+ return this.map((route) => {
+ return {
+ id: route.id,
+ path: route.path,
+ layout: route.layout,
+ getData: !!route.getData,
+ getMeta: !!route.getMeta,
+ onEnter: !!route.onEnter,
+ }
+ })
+ }
+ }
+ const importPaths = Object.keys(from)
+ const promises = []
+ if (Array.isArray(from)) {
+ for (const routeDef of from) {
+ promises.push(
+ getRouteModule(routeDef.path, routeDef.component)
+ .then((routeModule) => {
+ return {
+ id: routeDef.path,
+ path: routeDef.path ?? routeModule.path,
+ ...routeModule,
+ }
+ }),
+ )
+ }
+ } else {
+ // Ensure that static routes have precedence over the dynamic ones
+ for (const path of importPaths.sort((a, b) => a > b ? -1 : 1)) {
+ promises.push(
+ getRouteModule(path, from[path])
+ .then((routeModule) => {
+ return {
+ id: path,
+ layout: routeModule.layout,
+ path: routeModule.path ?? path
+ // Remove /pages and .jsx extension
+ .slice(6, -4)
+ // Replace [id] with :id
+ .replace(param, (_, m) => `:${m}`)
+ // Replace '/index' with '/'
+ .replace(/\/index$/, '/')
+ // Remove trailing slashs
+ .replace(/.+\/+$/, ''),
+ ...routeModule,
+ }
+ }),
+ )
+ }
+ }
+ return new Routes(...await Promise.all(promises))
+}
+
+async function hydrateRoutes (from) {
+ if (Array.isArray(from)) {
+ from = Object.fromEntries(
+ from.map((route) => [route.path, route]),
+ )
+ }
+ return window.routes.map((route) => {
+ route.loader = memoImport(from[route.id])
+ route.component = lazy(() => route.loader())
+ return route
+ })
+}
+
+function getRouteModuleExports (routeModule) {
+ return {
+ // The Route component (default export)
+ component: routeModule.default,
+ // The Layout Route component
+ layout: routeModule.layout,
+ // Route-level hooks
+ getData: routeModule.getData,
+ getMeta: routeModule.getMeta,
+ onEnter: routeModule.onEnter,
+ // Other Route-level settings
+ streaming: routeModule.streaming,
+ clientOnly: routeModule.clientOnly,
+ serverOnly: routeModule.serverOnly,
+ }
+}
+
+async function getRouteModule (path, routeModule) {
+ // const isServer = typeof process !== 'undefined'
+ if (typeof routeModule === 'function') {
+ routeModule = await routeModule()
+ return getRouteModuleExports(routeModule)
+ } else {
+ return getRouteModuleExports(routeModule)
+ }
+}
+
+function memoImport (func) {
+ // Otherwise we get a ReferenceError, but since this function
+ // is only ran once for each route, there's no overhead
+ const kFuncExecuted = Symbol('kFuncExecuted')
+ const kFuncValue = Symbol('kFuncValue')
+ func[kFuncExecuted] = false
+ return async function () {
+ if (!func[kFuncExecuted]) {
+ func[kFuncValue] = await func()
+ func[kFuncExecuted] = true
+ }
+ return func[kFuncValue]
+ }
+}
diff --git a/starters/vue/.eslintignore b/starters/vue/.eslintignore
new file mode 100644
index 0000000..53c37a1
--- /dev/null
+++ b/starters/vue/.eslintignore
@@ -0,0 +1 @@
+dist
\ No newline at end of file
diff --git a/starters/vue/.eslintrc b/starters/vue/.eslintrc
new file mode 100644
index 0000000..be9e0ff
--- /dev/null
+++ b/starters/vue/.eslintrc
@@ -0,0 +1,31 @@
+{
+ parser: '@babel/eslint-parser',
+ parserOptions: {
+ requireConfigFile: false,
+ ecmaVersion: 2021,
+ sourceType: 'module',
+ babelOptions: {
+ presets: ['@babel/preset-react'],
+ },
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ extends: [
+ 'plugin:react/recommended',
+ 'standard',
+ ],
+ plugins: [
+ 'react',
+ ],
+ rules: {
+ 'comma-dangle': ['error', 'always-multiline'],
+ 'react/prop-types': 'off',
+ 'import/no-absolute-path': 'off',
+ },
+ settings: {
+ react: {
+ version: '18.0',
+ },
+ },
+}
diff --git a/starters/vue/client/assets/logo.svg b/starters/vue/client/assets/logo.svg
new file mode 100644
index 0000000..9f2f2fd
--- /dev/null
+++ b/starters/vue/client/assets/logo.svg
@@ -0,0 +1,84 @@
+
+
diff --git a/starters/vue/client/base.css b/starters/vue/client/base.css
new file mode 100644
index 0000000..fde0cfc
--- /dev/null
+++ b/starters/vue/client/base.css
@@ -0,0 +1,56 @@
+:root {
+ --color-base: #f1f1f1;
+ --color-highlight: #ff80ff;
+}
+html {
+ background: #222;
+}
+main {
+ width: 800px;
+ margin: 0 auto;
+ padding: 2em;
+ box-shadow: 5px 5px 30px rgba(0,0,0,0.4);
+ border-radius: 10px;
+ background-color: rgba(255, 255, 255, 0.1);
+ font-family: Avenir, Helvetica, Arial, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ color: var(--color-base);
+ margin-top: 60px;
+ & a {
+ color: var(--color-highlight);
+ text-decoration: none;
+ font-weight: bold;
+ border-bottom: 1px solid var(--color-highlight);
+ &:hover {
+ color: #ffde00;
+ }
+ &:active {
+ color: #eecf00
+ }
+ }
+ & p {
+ font-size: 1.2em;
+ }
+ & ul {
+ & li {
+ &:not(:last-child) {
+ margin-bottom: 0.5em;
+ }
+ break-inside: avoid;
+ font-size: 1em;
+ }
+ }
+ & code {
+ color: #ffde00;
+ font-weight: bold;
+ font-family: 'Consolas', 'Andale Mono', monospace;
+ font-size: 0.9em;
+ }
+ & img {
+ width: 14em;
+ }
+ & button {
+ margin: 0 0.5em;
+ }
+}
diff --git a/starters/vue/client/context.js b/starters/vue/client/context.js
new file mode 100644
index 0000000..acb9cf7
--- /dev/null
+++ b/starters/vue/client/context.js
@@ -0,0 +1,36 @@
+import ky from 'ky-universal'
+
+export default (ctx) => {
+ if (ctx.server) {
+ ctx.state.todoList = ctx.server.db.todoList
+ }
+}
+
+export const $fetch = ky.extend({
+ prefixUrl: 'http://localhost:3000'
+})
+
+export const state = () => ({
+ user: {
+ authenticated: false,
+ },
+ todoList: null,
+})
+
+export const actions = {
+ authenticate (state) {
+ state.user.authenticated = true
+ },
+ async addTodoItem (state, item) {
+ await $fetch.put('api/todo/items', {
+ json: { item },
+ })
+ state.todoList.push(item)
+ },
+ async removeTodoItem (state, index) {
+ await $fetch.delete('api/todo/items', {
+ json: { index },
+ })
+ state.todoList.splice(index, 1)
+ }
+}
diff --git a/starters/vue/client/index.html b/starters/vue/client/index.html
new file mode 100644
index 0000000..19bcdb9
--- /dev/null
+++ b/starters/vue/client/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/starters/vue/client/index.js b/starters/vue/client/index.js
new file mode 100644
index 0000000..425bf49
--- /dev/null
+++ b/starters/vue/client/index.js
@@ -0,0 +1,8 @@
+import routes from '/dx:routes.js'
+import create from '/dx:create.jsx'
+
+export default {
+ context: import('/dx:context.js'),
+ routes,
+ create,
+}
diff --git a/starters/vue/client/layouts/auth.jsx b/starters/vue/client/layouts/auth.jsx
new file mode 100644
index 0000000..517989a
--- /dev/null
+++ b/starters/vue/client/layouts/auth.jsx
@@ -0,0 +1,25 @@
+import { Suspense } from 'react'
+import { useRouteContext } from '/dx:core.jsx'
+
+export default function Auth ({ children }) {
+ const { actions, state, snapshot } = useRouteContext()
+ const authenticate = () => actions.authenticate(state)
+ return (
+
+ {snapshot.user.authenticated
+ ? children
+ : authenticate()} /> }
+
+ )
+}
+
+function Login ({ onClick }) {
+ return (
+ <>
+
This route needs authentication.
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/starters/vue/client/layouts/default.jsx b/starters/vue/client/layouts/default.jsx
new file mode 100644
index 0000000..604b629
--- /dev/null
+++ b/starters/vue/client/layouts/default.jsx
@@ -0,0 +1,9 @@
+import { Suspense } from 'react'
+
+export default function Default ({ children }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/starters/vue/client/pages/client-only.jsx b/starters/vue/client/pages/client-only.jsx
new file mode 100644
index 0000000..b55bf1d
--- /dev/null
+++ b/starters/vue/client/pages/client-only.jsx
@@ -0,0 +1,23 @@
+import { Link } from 'react-router-dom'
+
+export const clientOnly = true
+
+export function getMeta () {
+ return {
+ title: 'Client Only Page'
+ }
+}
+
+export default function ClientOnly () {
+ return (
+ <>
+
This route is rendered on the client only!
+
+ Go back to the index
+
+
⁂
+
When this route is rendered on the server, no SSR takes place.
+
See the output of curl http://localhost:3000/client-only.
+ >
+ )
+}
diff --git a/starters/vue/client/pages/index.jsx b/starters/vue/client/pages/index.jsx
new file mode 100644
index 0000000..2b36de5
--- /dev/null
+++ b/starters/vue/client/pages/index.jsx
@@ -0,0 +1,35 @@
+import logo from '/assets/logo.svg'
+import { Link } from 'react-router-dom'
+
+export function getMeta () {
+ return {
+ title: 'Welcome to Fastify DX!'
+ }
+}
+
+export default function Index () {
+ return (
+ <>
+
+
Welcome to Fastify DX for React!
+
+
/using-data demonstrates how to
+ leverage the getData() function
+ and useRouteContext() to retrieve server data for a route.
+
/using-store demonstrates how to
+ leverage the
+ automated Valtio store
+ to retrieve server data for a route and maintain it in a global
+ state even after navigating to another route.
+
/using-auth demonstrates how to
+ wrap a route in a custom layout component.
+
/client-only demonstrates how to set
+ up a route for rendering on the client only (disables SSR).
+
/server-only demonstrates how to set
+ up a route for rendering on the server only (sends no JavaScript).
+
/streaming demonstrates how to set
+ up a route for SSR in streaming mode.
+
+ >
+ )
+}
diff --git a/starters/vue/client/pages/server-only.jsx b/starters/vue/client/pages/server-only.jsx
new file mode 100644
index 0000000..bb65b74
--- /dev/null
+++ b/starters/vue/client/pages/server-only.jsx
@@ -0,0 +1,17 @@
+import { Link } from 'react-router-dom'
+
+export const serverOnly = true
+
+export default function ServerOnly () {
+ return (
+ <>
+
This route is rendered on the server only!
+
+ Go back to the index
+
+
⁂
+
When this route is rendered on the server, no JavaScript is sent to the client.
+
See the output of curl http://localhost:3000/server-only.
+ >
+ )
+}
diff --git a/starters/vue/client/pages/streaming.jsx b/starters/vue/client/pages/streaming.jsx
new file mode 100644
index 0000000..99da054
--- /dev/null
+++ b/starters/vue/client/pages/streaming.jsx
@@ -0,0 +1,46 @@
+import { Suspense } from 'react'
+
+export const streaming = true
+
+export default function Index () {
+ return (
+ Waiting for content
This example is exactly the same as /using-store,
+ except it's wrapped in a custom layout which blocks it until
+ user.authenticated is true in the global state.
+ >
+ )
+}
diff --git a/starters/vue/client/pages/using-data.jsx b/starters/vue/client/pages/using-data.jsx
new file mode 100644
index 0000000..b380e85
--- /dev/null
+++ b/starters/vue/client/pages/using-data.jsx
@@ -0,0 +1,46 @@
+import { useState } from 'react'
+import { Link } from 'react-router-dom'
+import { useRouteContext } from '/dx:core.jsx'
+
+export function getMeta () {
+ return { title: 'Todo List — Using Data' }
+}
+
+export function getData ({ server }) {
+ return {
+ todoList: server.db.todoList
+ }
+}
+
+export default function Index (props) {
+ const {data} = useRouteContext()
+ const [todoList, updateTodoList] = useState(data.todoList)
+ const [input, setInput] = useState(null)
+ const addItem = (value) => {
+ updateTodoList(list => [...list, value])
+ input.value = ''
+ }
+ return (
+ <>
+
Todo List — Using Data
+
{
+ todoList.map((item, i) => {
+ return
{item}
+ })
+ }
+
+
+
+
+
+ Go back to the index
+
+
⁂
+
When you navigate away from this route, any additions to the to-do
+ list will be lost, because they're bound to this route component only.
+
See the /using-store example to learn
+ how to use the application global state for it.
+
+ >
+ )
+}
diff --git a/starters/vue/client/pages/using-store.jsx b/starters/vue/client/pages/using-store.jsx
new file mode 100644
index 0000000..b530216
--- /dev/null
+++ b/starters/vue/client/pages/using-store.jsx
@@ -0,0 +1,36 @@
+import { useState } from 'react'
+import { Link } from 'react-router-dom'
+import { useRouteContext } from '/dx:core.jsx'
+
+export function getMeta () {
+ return { title: 'Todo List — Using Store' }
+}
+
+export default function Index (props) {
+ const {snapshot, state, actions} = useRouteContext()
+ const [input, setInput] = useState(null)
+ const addItem = async (value) => {
+ await actions.addTodoItem(state, value)
+ input.value = ''
+ }
+ return (
+ <>
+
Todo List — Using Store
+
{
+ snapshot.todoList.map((item, i) => {
+ return
{item}
+ })
+ }
+
+
+
+
+
+ Go back to the index
+
+
⁂
+
When you navigate away from this route, any additions to the to-do
+ list are not lost, because they're bound to the global application state.
When this route is rendered on the server, no SSR takes place.
+
See the output of curl http://localhost:3000/client-only.
+
+
+
\ No newline at end of file
diff --git a/starters/vue/client/pages/index.jsx b/starters/vue/client/pages/index.jsx
deleted file mode 100644
index 2b36de5..0000000
--- a/starters/vue/client/pages/index.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import logo from '/assets/logo.svg'
-import { Link } from 'react-router-dom'
-
-export function getMeta () {
- return {
- title: 'Welcome to Fastify DX!'
- }
-}
-
-export default function Index () {
- return (
- <>
-
-
Welcome to Fastify DX for React!
-
-
/using-data demonstrates how to
- leverage the getData() function
- and useRouteContext() to retrieve server data for a route.
-
/using-store demonstrates how to
- leverage the
- automated Valtio store
- to retrieve server data for a route and maintain it in a global
- state even after navigating to another route.
-
/using-auth demonstrates how to
- wrap a route in a custom layout component.
-
/client-only demonstrates how to set
- up a route for rendering on the client only (disables SSR).
-
/server-only demonstrates how to set
- up a route for rendering on the server only (sends no JavaScript).
-
/streaming demonstrates how to set
- up a route for SSR in streaming mode.
This example is exactly the same as /using-store,
- except it's wrapped in a custom layout which blocks it until
- user.authenticated is true in the global state.
This example is exactly the same as /using-store,
+ except it's wrapped in a custom layout which blocks it until
+ user.authenticated is true in the global state.
When you navigate away from this route, any additions to the to-do
+ list are not lost, because they're bound to the global application state.
+
+
+
diff --git a/starters/vue/client/root.jsx b/starters/vue/client/root.jsx
deleted file mode 100644
index 00cef47..0000000
--- a/starters/vue/client/root.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'virtual:uno.css'
-import { Suspense } from 'react'
-import { DXApp } from '/dx:core.jsx'
-
-export default function Root ({ url, serverInit }) {
- return (
-
-
-
- )
-}
diff --git a/starters/vue/client/root.vue b/starters/vue/client/root.vue
new file mode 100644
index 0000000..d56013a
--- /dev/null
+++ b/starters/vue/client/root.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/starters/vue/package.json b/starters/vue/package.json
index 7eeff14..50d40e0 100644
--- a/starters/vue/package.json
+++ b/starters/vue/package.json
@@ -10,36 +10,33 @@
"lint": "eslint . --ext .js,.jsx --fix"
},
"dependencies": {
- "fastify-dx-react": "^0.0.2",
"fastify-vite": "^3.0.0-beta.23",
- "ky-universal": "^0.10.1"
- },
- "distDependencies": {
- "fastify-dx-react": "^0.0.2",
- "fastify-vite": "^3.0.0-beta.23",
- "ky-universal": "^0.10.1"
+ "ky-universal": "^0.10.1",
+ "@vue/server-renderer": "^3.2.33",
+ "devalue": "^2.0.1",
+ "vue-router": "^4.0.15",
+ "unihead": "^0.0.6"
},
"devDependencies": {
"@babel/eslint-parser": "^7.16.0",
- "@babel/preset-react": "^7.16.0",
- "@vitejs/plugin-react": "^1.3.2",
+ "@vitejs/plugin-vue": "^2.3.2",
"eslint": "^7.32.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
- "eslint-plugin-react": "^7.29.4",
+ "eslint-plugin-vue": "^8.7.1",
"postcss-preset-env": "^7.7.1",
- "unocss": "^0.37.4",
- "vite-plugin-blueprint": "^0.0.4"
+ "unocss": "^0.37.4"
},
"devInstall": {
"local": {
- "fastify-dx-react": "^0.0.1-alpha.0"
+ "fastify-dx-vue": "^0.0.3"
},
"external": {
"fastify-vite": "^3.0.0-beta.23",
- "ky-universal": "^0.10.1"
+ "ky-universal": "^0.10.1",
+ "@vueuse/core": "^8.7.4"
}
}
}
\ No newline at end of file
diff --git a/starters/vue/server.js b/starters/vue/server.js
index 00c8744..c7ae4e3 100644
--- a/starters/vue/server.js
+++ b/starters/vue/server.js
@@ -1,6 +1,6 @@
import Fastify from 'fastify'
import FastifyVite from 'fastify-vite'
-import FastifyDXReact from 'fastify-dx-react'
+import FastifyDXVue from 'fastify-dx-vue'
const server = Fastify()
@@ -24,7 +24,7 @@ server.delete('/api/todo/items', (req, reply) => {
await server.register(FastifyVite, {
root: import.meta.url,
- renderer: FastifyDXReact,
+ renderer: FastifyDXVue,
})
await server.vite.ready()
diff --git a/starters/vue/vite.config.js b/starters/vue/vite.config.js
index 7a22ee3..5090b69 100644
--- a/starters/vue/vite.config.js
+++ b/starters/vue/vite.config.js
@@ -1,29 +1,20 @@
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
-import viteReact from '@vitejs/plugin-react'
-import viteReactFastifyDX from 'fastify-dx-react/plugin'
+import viteVue from '@vitejs/plugin-vue'
+import viteVueFastifyDX from 'fastify-dx-vue/plugin'
import unocss from 'unocss/vite'
const path = fileURLToPath(import.meta.url)
const root = join(dirname(path), 'client')
const plugins = [
- viteReact({
- // Necessary until this Vite issue is resolved:
- // https://github.com/vitejs/vite/issues/3301#issuecomment-1080292430
- fastRefresh: false,
- }),
- viteReactFastifyDX(),
- unocss()
+ unocss(),
+ viteVue(),
+ viteVueFastifyDX(),
]
export default {
root,
plugins,
- ssr: {
- external: [
- 'use-sync-external-store'
- ]
- },
}
From 2ed93c255c3103bbc00deaf3ce0e868b6e322c71 Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Sun, 19 Jun 2022 17:16:50 -0300
Subject: [PATCH 04/14] cleanups
---
packages/fastify-dx-vue/virtual/routes.js | 1 -
starters/vue/client/mount.js | 38 +++++++++++++++++++++++
starters/vue/client/pages/index.vue | 2 +-
starters/vue/client/pages/using-data.vue | 5 ++-
starters/vue/package.json | 1 +
starters/vue/vite.config.js | 2 +-
6 files changed, 43 insertions(+), 6 deletions(-)
create mode 100644 starters/vue/client/mount.js
diff --git a/packages/fastify-dx-vue/virtual/routes.js b/packages/fastify-dx-vue/virtual/routes.js
index 4ec3b41..eae15e7 100644
--- a/packages/fastify-dx-vue/virtual/routes.js
+++ b/packages/fastify-dx-vue/virtual/routes.js
@@ -71,7 +71,6 @@ async function hydrateRoutes (from) {
}
return window.routes.map((route) => {
route.loader = memoImport(from[route.id])
- console.log('from[route.id]', from[route.id])
route.component = from[route.id]
return route
})
diff --git a/starters/vue/client/mount.js b/starters/vue/client/mount.js
new file mode 100644
index 0000000..c23b8d4
--- /dev/null
+++ b/starters/vue/client/mount.js
@@ -0,0 +1,38 @@
+import Head from 'unihead/client'
+import create from '/dx:create.js'
+import routesPromise from '/dx:routes.js'
+import * as context from '/dx:context.js'
+
+mount('main')
+
+async function mount (target) {
+ const ctxHydration = await extendContext(window.route, context)
+ const head = new Head(window.route.head, window.document)
+ const resolvedRoutes = await routesPromise
+ const routeMap = Object.fromEntries(
+ resolvedRoutes.map((route) => [route.path, route]),
+ )
+ const { instance, router } = await create({
+ head,
+ ctxHydration,
+ routes: window.routes,
+ routeMap,
+ })
+ await router.isReady()
+ instance.mount(target)
+}
+
+async function extendContext (ctx, {
+ // The route context initialization function
+ default: setter,
+ // We destructure state here just to discard it from extra
+ state,
+ // Other named exports from context.js
+ ...extra
+}) {
+ Object.assign(ctx, extra)
+ if (setter) {
+ await setter(ctx)
+ }
+ return ctx
+}
diff --git a/starters/vue/client/pages/index.vue b/starters/vue/client/pages/index.vue
index 3b5d6bc..c324257 100644
--- a/starters/vue/client/pages/index.vue
+++ b/starters/vue/client/pages/index.vue
@@ -6,7 +6,7 @@
leverage the getData() function
and useRouteContext() to retrieve server data for a route.
/using-store demonstrates how to
- retrieve server data and maintain it in the global state (automatically hydrated).
+ retrieve server data and maintain it in the global state.
/using-auth demonstrates how to
wrap a route in a custom layout component.
/client-only demonstrates how to set
diff --git a/starters/vue/client/pages/using-data.vue b/starters/vue/client/pages/using-data.vue
index da05d5d..bba270a 100644
--- a/starters/vue/client/pages/using-data.vue
+++ b/starters/vue/client/pages/using-data.vue
@@ -38,10 +38,9 @@ export function getData ({ server }) {
export default {
setup () {
- const routeContext = useRouteContext()
- console.log('->routeContext->', routeContext)
+ const { data } = useRouteContext()
const inputValue = ref(null)
- const todoList = reactive(routeContext.data.todoList)
+ const todoList = reactive(data.todoList)
const addItem = () => {
todoList.push(inputValue.value)
inputValue.value = ''
diff --git a/starters/vue/package.json b/starters/vue/package.json
index 50d40e0..67fe0a1 100644
--- a/starters/vue/package.json
+++ b/starters/vue/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"fastify-vite": "^3.0.0-beta.23",
"ky-universal": "^0.10.1",
+ "@vueuse/core": "^8.7.4",
"@vue/server-renderer": "^3.2.33",
"devalue": "^2.0.1",
"vue-router": "^4.0.15",
diff --git a/starters/vue/vite.config.js b/starters/vue/vite.config.js
index 5090b69..51e4b35 100644
--- a/starters/vue/vite.config.js
+++ b/starters/vue/vite.config.js
@@ -9,8 +9,8 @@ const path = fileURLToPath(import.meta.url)
const root = join(dirname(path), 'client')
const plugins = [
- unocss(),
viteVue(),
+ unocss(),
viteVueFastifyDX(),
]
From a048ee7ed905c113f38b1d34540a97459818de77 Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Sun, 19 Jun 2022 21:29:09 -0300
Subject: [PATCH 05/14] wip vue
---
packages/fastify-dx-vue/virtual/core.js | 11 +++++------
packages/fastify-dx-vue/virtual/create.js | 8 +++++---
packages/fastify-dx-vue/virtual/layout.vue | 9 ++++++---
packages/fastify-dx-vue/virtual/root.vue | 5 +----
starters/vue/client/root.vue | 5 +----
5 files changed, 18 insertions(+), 20 deletions(-)
diff --git a/packages/fastify-dx-vue/virtual/core.js b/packages/fastify-dx-vue/virtual/core.js
index 39c6cbc..0e47c5b 100644
--- a/packages/fastify-dx-vue/virtual/core.js
+++ b/packages/fastify-dx-vue/virtual/core.js
@@ -4,6 +4,7 @@ import { useRoute, createMemoryHistory, createWebHistory } from 'vue-router'
export const isServer = typeof process === 'object'
export const createHistory = isServer ? createMemoryHistory : createWebHistory
export const serverRouteContext = Symbol('serverRouteContext')
+export const routeLayout = Symbol('routeLayout')
export function useRouteContext () {
if (isServer) {
@@ -13,11 +14,7 @@ export function useRouteContext () {
}
}
-export function createBeforeEachHandler ({
- routeMap,
- ctxHydration,
- head,
-}) {
+export function createBeforeEachHandler ({ routeMap, ctxHydration, head }, layout) {
return async function beforeCreate (to) {
// The client-side route context
const ctx = routeMap[to.matched[0].path]
@@ -26,7 +23,9 @@ export function createBeforeEachHandler ({
ctx.state = ctxHydration.state
ctx.actions = ctxHydration.actions
- ctx.layout ??= 'default'
+
+ // Update layoutRef
+ layout.value = ctx.layout ?? 'default'
// If it is, take server context data from hydration and return immediately
if (ctx.firstRender) {
diff --git a/packages/fastify-dx-vue/virtual/create.js b/packages/fastify-dx-vue/virtual/create.js
index 405c5e4..0d7e2ba 100644
--- a/packages/fastify-dx-vue/virtual/create.js
+++ b/packages/fastify-dx-vue/virtual/create.js
@@ -1,9 +1,10 @@
-import { createApp, createSSRApp, reactive } from 'vue'
+import { createApp, createSSRApp, reactive, ref } from 'vue'
import { createRouter } from 'vue-router'
import {
isServer,
createHistory,
serverRouteContext,
+ routeLayout,
createBeforeEachHandler,
} from '/dx:core.js'
import root from '/dx:root.vue'
@@ -17,14 +18,15 @@ export default async function create (ctx) {
const history = createHistory()
const router = createRouter({ history, routes })
+ const layoutRef = ref(ctxHydration.layout ?? 'default')
- ctxHydration.layout ??= 'default'
+ instance.provide(routeLayout, layoutRef)
ctxHydration.state = reactive(ctxHydration.state)
if (isServer) {
instance.provide(serverRouteContext, ctxHydration)
} else {
- router.beforeEach(createBeforeEachHandler(ctx))
+ router.beforeEach(createBeforeEachHandler(ctx, layoutRef))
}
instance.use(router)
diff --git a/packages/fastify-dx-vue/virtual/layout.vue b/packages/fastify-dx-vue/virtual/layout.vue
index e0d5b21..8a12b65 100644
--- a/packages/fastify-dx-vue/virtual/layout.vue
+++ b/packages/fastify-dx-vue/virtual/layout.vue
@@ -1,11 +1,12 @@
-
+
-
+
import Layout from '/dx:layout.vue'
-import { useRouteContext } from '/dx:core.js'
-
-const { layout } = useRouteContext()
-
+
Date: Sun, 19 Jun 2022 23:41:34 -0300
Subject: [PATCH 06/14] wip vue
---
docs/vue/basic-setup.md | 58 +++++++++++
docs/vue/data-prefetching.md | 36 +++++++
docs/vue/index.md | 89 ++++++++++++++++
docs/vue/meta-tags.md | 33 ++++++
docs/vue/project-structure.md | 63 +++++++++++
docs/vue/rendering-modes.md | 69 ++++++++++++
docs/vue/route-context.md | 95 +++++++++++++++++
docs/vue/route-enter.md | 27 +++++
docs/vue/route-layouts.md | 56 ++++++++++
docs/vue/routing-config.md | 49 +++++++++
docs/vue/virtual-modules.md | 191 ++++++++++++++++++++++++++++++++++
11 files changed, 766 insertions(+)
create mode 100644 docs/vue/basic-setup.md
create mode 100644 docs/vue/data-prefetching.md
create mode 100644 docs/vue/index.md
create mode 100644 docs/vue/meta-tags.md
create mode 100644 docs/vue/project-structure.md
create mode 100644 docs/vue/rendering-modes.md
create mode 100644 docs/vue/route-context.md
create mode 100644 docs/vue/route-enter.md
create mode 100644 docs/vue/route-layouts.md
create mode 100644 docs/vue/routing-config.md
create mode 100644 docs/vue/virtual-modules.md
diff --git a/docs/vue/basic-setup.md b/docs/vue/basic-setup.md
new file mode 100644
index 0000000..02c8250
--- /dev/null
+++ b/docs/vue/basic-setup.md
@@ -0,0 +1,58 @@
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Basic setup
+
+The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/vue) follows [fastify-vite](https://github.com/fastify/fastify-vite)'s convention of having a `client` folder with an `index.js` file, which is automatically resolved as your `clientModule` setting.
+
+If you want flat directory setup, where server and client files are mixed together, you can manually set `clientModule` to something else. Note that in this case you'll also need to update `root` in your `vite.config.js` file.
+
+When deploying to production, bear in mind the `client/dist` directory, generated when you run `npm run build`, needs to be included. You'll also want to enable Fastify's [built-in logging](https://www.fastify.io/docs/latest/Reference/Logging/):
+
+```js
+const server = Fastify({ logger: true })
+```
+
+The starter template's `server.js` file:
+
+```js
+import Fastify from 'fastify'
+import FastifyVite from 'fastify-vite'
+import FastifyDXVue from 'fastify-dx-vue'
+
+const server = Fastify()
+
+await server.register(FastifyVite, {
+ root: import.meta.url,
+ renderer: FastifyDXVue,
+})
+
+await server.vite.ready()
+await server.listen(3000)
+```
+
+The starter template's [`vite.config.js`](https://github.com/fastify/fastify-dx/blob/main/starters/vue/vite.config.js) file:
+
+```js
+import viteReact from '@vitejs/plugin-react'
+import viteVueFastifyDX from 'fastify-dx-vue/plugin'
+import unocss from 'unocss/vite'
+
+const path = fileURLToPath(import.meta.url)
+
+const root = join(dirname(path), 'client')
+const plugins = [
+ viteReact(),
+ viteVueFastifyDX(),
+ unocss()
+]
+
+export default { root, plugins }
+```
+
+Note that you only need to use Fastify DX's Vite plugin, which includes all functionality from [fastify-vite](https://github.com/fastify/fastify-vite)'s Vite plugin.
+
+
+
+
diff --git a/docs/vue/data-prefetching.md b/docs/vue/data-prefetching.md
new file mode 100644
index 0000000..6eae389
--- /dev/null
+++ b/docs/vue/data-prefetching.md
@@ -0,0 +1,36 @@
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Isomorphic data prefetching
+
+Fastify DX for Vue implements the `getData()` hook from the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md) to solve this problem.
+
+### `getData(ctx)`
+
+This hook is set up in a way that it runs server-side before any SSR takes place, so any data fetched is made available to the route component before it starts rendering. During first render, any data retrieved on the server is automatically sent to be hydrated on the client so no new requests are made. Then, during client-side navigation (post first-render), a JSON request is fired to an endpoint automatically registered for running the `getData()` function for that route on the server.
+
+The objet returned by `getData()` gets automatically assigned as `data` in the [universal route context](https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md) object and is accessible from `getMeta()` and `onEnter()` hooks and also via the `useRouteContext()` hook.
+
+```vue
+
+
{data.message}
+
+
+
+```
diff --git a/docs/vue/index.md b/docs/vue/index.md
new file mode 100644
index 0000000..91dd7b9
--- /dev/null
+++ b/docs/vue/index.md
@@ -0,0 +1,89 @@
+# fastify-dx-vue [![NPM version](https://img.shields.io/npm/v/fastify-dx-vue.svg?style=flat)](https://www.npmjs.com/package/fastify-dx-vue) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
+
+- [**Introduction**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md#introduction)
+- [**Quick Start**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md#quick-start)
+- [**Package Scripts**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md#package-scripts)
+- [**Basic Setup**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/basic-setup.md)
+- [**Project Structure**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/project-structure.md)
+- [**Rendering Modes**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/rendering-modes.md)
+- [**Routing Configuration**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/routing-config.md)
+- [**Data Prefetching**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/data-prefetching.md)
+- [**Route Layouts**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-layouts.md)
+- [**Route Context**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md)
+- [**Route Enter Event**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-enter.md)
+- [**Virtual Modules**](https://github.com/fastify/fastify-dx/blob/main/docs/vue/virtual-modules.md)
+
+## Introduction
+
+**Fastify DX for Vue** is a renderer adapter for [**fastify-vite**](https://github.com/fastify/fastify-vite).
+
+It is a **fast**, **lightweight** alternative to Nuxt.js packed with **Developer Experience** features.
+
+It has an extremely small core (~1k LOC total) and is built on top of [Fastify](https://github.com/fastify/fastify), [Vite](https://vitejs.dev/) and [Vue Router](https://router.vuejs.org/).
+
+[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/v0.0.1).
+
+> At this stage this project is mostly a [**one-man show**](https://github.com/sponsors/galvez), who's devoting all his free time to its completion. Contributions are extremely welcome, as well as bug reports for any issues you may find.
+
+In this first alpha release it's still missing a test suite. The same is true for [**fastify-vite**]().
+
+It'll move into **beta** status when test suites are added to both packages.
+
+## Quick Start
+
+Ensure you have **Node v16+**.
+
+Make a copy of [**starters/vue**](https://github.com/fastify/fastify-dx/tree/dev/starters/vue). If you have [`degit`](https://github.com/Rich-Harris/degit), run the following from a new directory:
+
+```bash
+degit fastify/fastify-dx/starters/vue
+```
+
+> **If you're starting a project from scratch**, you'll need these packages installed.
+>
+> ```bash
+> npm i fastify fastify-vite fastify-dx-vue -P
+> npm i @vitejs/plugin-vue -D
+> ```
+
+
+Run `npm install`.
+
+Run `npm run dev`.
+
+Visit `http://localhost:3000/`.
+
+## What's Included
+
+That will get you a **starter template** with:
+
+- A minimal [Fastify](https://github.com/fastify/fastify) server.
+- Some dummy API routes.
+- A `pages/` folder with some [demo routes](https://github.com/fastify/fastify-dx/tree/dev/starters/vue/client/pages).
+- All configuration files.
+
+It also includes some _**opinionated**_ essentials:
+
+- [**PostCSS Preset Env**](https://www.npmjs.com/package/postcss-preset-env) by [**Jonathan Neal**](https://github.com/jonathantneal), which enables [several modern CSS features](https://preset-env.cssdb.org/), such as [**CSS Nesting**](https://www.w3.org/TR/css-nesting-1/).
+
+- [**UnoCSS**](https://github.com/unocss/unocss) by [**Anthony Fu**](https://antfu.me/), which supports all [Tailwind utilities](https://uno.antfu.me/) and many other goodies through its [default preset](https://github.com/unocss/unocss/tree/main/packages/preset-uno).
+
+- [**VueUse**](https://vueuse.org/) by [**Anthony Fu**](https://antfu.me/), which provides an extremely rich set of utilities — they're not included in the project build unless explicitly imported and used.
+
+## Package Scripts
+
+`npm run dev` boots the development server.
+
+`npm run build` creates the production bundle.
+
+`npm run serve` serves the production bundle.
+
+## Meta
+
+Created by [Jonas Galvez](https://github.com/sponsors/galvez), **Engineering Manager** and **Open Sourcerer** at [NearForm](https://nearform.com).
+
+## Sponsors
+
+
+
+Also [**Duc-Thien Bui**](https://github.com/aecea) and [**Tom Preston-Werner**](https://github.com/mojombo) [via GitHub Sponsors](https://github.com/sponsors/galvez). _Thank you!_
diff --git a/docs/vue/meta-tags.md b/docs/vue/meta-tags.md
new file mode 100644
index 0000000..aae4487
--- /dev/null
+++ b/docs/vue/meta-tags.md
@@ -0,0 +1,33 @@
+
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Meta Tags
+
+Following the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md), Fastify DX renders `` elements independently from the SSR phase. This allows you to fetch data for populating the first `` tags and stream them right away to the client, and only then perform SSR.
+
+> Additional `` preload tags can be produced from the SSR phase. This is **not currently implemented** in this **alpha release** but is a planned feature. If you can't wait for it, you can roll out your own (and perhaps contribute your solution) by providing your own [`createHtmlFunction()`](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/index.js#L57) to [fastify-vite](https://github.com/fastify/fastify-vite).
+
+### `getMeta()`
+
+To populate ``, `` and `` elements, export a `getMeta()` function that returns an object matching the format expected by [unihead](https://github.com/galvez/unihead), the underlying library used by Fastify DX.
+
+It receives the [route context](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md#route-context) as first parameter and runs after `getData()`, allowing you to access any `data` populated by these other functions to generate your tags.
+
+```vue
+
+
Route with meta tags.
+
+
+
+```
diff --git a/docs/vue/project-structure.md b/docs/vue/project-structure.md
new file mode 100644
index 0000000..f8d6332
--- /dev/null
+++ b/docs/vue/project-structure.md
@@ -0,0 +1,63 @@
+
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Project Structure
+
+The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/vue) looks like this:
+
+```
+├── server.js
+├── client/
+| ├── index.js
+| ├── context.js
+| ├── root.vue
+| ├── index.html
+| ├── layouts/
+| | ├── default.vue
+| | └── auth.vue
+| └── pages/
+| ├── index.vue
+| ├── client-only.vue
+| ├── server-only.vue
+| ├── streaming.vue
+| ├── using-data.vue
+| └── using-store.vue
+├── vite.config.js
+└── package.json
+```
+
+Several internal files are provided as virtual modules by Fastify DX. They are located inside the `fastify-dx-vue` package in `node_modules`, and dynamically loaded so you don't have to worry about them unless you want them overriden.
+
+In this case, placing a file with the same name as the registered virtual module in your Vite project root will override it. Find the detailed rundown of all virtual modules [here][virtual-modules].
+
+[virtual-modules]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/virtual-modules.md
+
+The `server.js` file is your application entry point. It's the file that runs everything. It boots a Fastify server configured with [**fastify-vite**](https://github.com/fastify/fastify-vite) and **Fastify DX for React** as a renderer adapter to **fastify-vite**.
+
+The `client/index.js` file is your Vite server entry point, it's the file that provides your client bundle (which runs in the Vite-enriched environment) to the Node.js environment where Fastify runs.
+
+> Right now, it's mostly a **boilerplate file** because it must exist but it will also probably never need to be changed.
+
+It exports your application's factory function (must be named `create`), the application routes (must be named `routes`) and the universal route context [initialization module](https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md#initialization-module) (must be named `context` and have a dynamic module import so Fastify DX can pick up `default` and named exports).
+
+The `client/index.html` file is the [root HTML template of the application](https://vitejs.dev/guide/#index-html-and-project-root), which Vite uses as the client bundling entry point.
+
+> You can expand this file with additional `` and `` tags if you wish, provided you don't remove any of the placeholders.
+
+This files links to `/dx:mount.js`, which is a virtual module provided by Fastify DX.
+
+Virtual modules are covered [here][virtual-modules].
+
+The `client/pages/` directory contains your route modules, whose paths are dynamically inferred from the directory structure itself. You can change this behavior easily. More on this [here][routing-config].
+
+[routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/routing-config.md
+
+The `client/layouts/` directory contains your route layout modules, which can be associated to any route. By default, `layouts/default.jsx` is used, but if you don't need to do any modifications on that file, you can safely removed as it's provided by Fastify DX in that case. The starter template also comes with `layouts/auth.jsx`, to demonstrate a more advanced use of layouts.
+
+[routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/routing-config.md
+
+The `client/context.js` file is the universal [route context][route-context] initialization module. Any named exports from this file are attached to the `RouteContext` class prototype on the server, preventing them from being reassigned on every request. The `default` export from this file, however, runs for every request so you can attach any request-specific data to it.
+
+[route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md
diff --git a/docs/vue/rendering-modes.md b/docs/vue/rendering-modes.md
new file mode 100644
index 0000000..95bc1b6
--- /dev/null
+++ b/docs/vue/rendering-modes.md
@@ -0,0 +1,69 @@
+
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+# Rendering modes
+
+Following the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md), Fastify DX's route modules can be set for universal rendering (SSR + CSR hydration, the default behavior), SSR in streaming mode, SSR only (client gets no JavaScript) or CSR only (SSR fully disabled).
+
+## `streaming`
+
+If a route module exports `streaming` set to `true`, SSR will take place in **streaming mode**. That means the result of all server-side rendering gets streamed as it takes place, even if you have asynchronous Vue components. Note that differently from React, Vue **will not** stream a Suspense block's `#fallback` template.
+
+```vue
+
+
+
+
+
+
+
+```
+
+[See the full example](https://github.com/fastify/fastify-dx/blob/main/starters/vue/client/pages/streaming.vue) in the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/vue).
+
+
+## `serverOnly`
+
+If a route module exports `serverOnly` set to `true`, only SSR will take place. The client gets the server-side rendered markup without any accompanying JavaScript or data hydration.
+
+You should use this setting to deliver lighter pages when there's no need to run any code on them, such as statically generated content sites.
+
+```vue
+
+
This route is rendered on the server only!
+
+
+
+```
+
+[This example](https://github.com/fastify/fastify-dx/blob/main/starters/vue/client/pages/server-only.vue) is part of the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/vue).
+
+## `clientOnly`
+
+If a route module exports `clientOnly` set to `true`, no SSR will take place, only data fetching and data hydration. The client gets the empty container element (the one that wraps `` in `index.html`) and all rendering takes place on the client only.
+
+You can use this setting to save server resources on internal pages where SSR makes no significant diference for search engines or UX in general, such as a password-protected admin section.
+
+```vue
+
+
This route is rendered on the client only!
+
+
+
+```
+
+[This example](https://github.com/fastify/fastify-dx/blob/main/starters/vue/client/pages/client-only.vue) is part of the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/vue).
diff --git a/docs/vue/route-context.md b/docs/vue/route-context.md
new file mode 100644
index 0000000..358aabd
--- /dev/null
+++ b/docs/vue/route-context.md
@@ -0,0 +1,95 @@
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Route Context
+
+### Initialization module
+
+The starter template includes a sample `context.js` file. This file is optional and can be safely removed. If it's present, Fastify DX automatically loads it and uses it to do any RouteContext extensions or data injections you might need. If you're familiar with [Nuxt.js](https://nuxtjs.org/), you can think of it as a [Nuxt.js plugin](https://nuxtjs.org/docs/directory-structure/plugins/).
+
+**Consuming the route context:**
+
+```js
+import {
+ useRouteContext
+} from '/dx:core.js'
+
+// ...
+const {
+ state,
+ actions
+} = useRouteContext()
+
+// ...
+actions.addTodoItem(state, value)
+```
+
+See the [full example](https://github.com/fastify/fastify-dx/blob/main/starters/vue/client/pages/using-store.vue) in the starter template.
+
+This example demonstrates how to use it to set up an universally available (SSR and CSR) `$fetch` function (using [`ky-universal`](https://www.npmjs.com/package/ky-universal)) and also export some store actions. They're all made available by `useRouteContext()`, covered next.
+
+```js
+import ky from 'ky-universal'
+
+export default (ctx) => {
+ // This runs both on the server and on the
+ // client, exactly once per HTTP request
+ ctx.message = 'Universal hello'
+ if (ctx.server) {
+ // Place some server data on the
+ // application's global state
+ ctx.state = ctx.server.db
+ // It is automatically hydrated on the client
+ // So no need to any additional assignments here
+ }
+}
+
+export const $fetch = ky.extend({
+ prefixUrl: 'http://localhost:3000'
+})
+
+export async function addTodoItem (state, item) {
+ await $fetch.put('api/todo/items', {
+ body: { item },
+ })
+ state.todoList.push(item)
+}
+```
+
+See the [full example](https://github.com/fastify/fastify-dx/blob/main/starters/vue/client/context.js) in the starter template.
+
+### The `useRouteContext()` hook
+
+This hook can be used in any Vue component to retrieve a reference to the current route context. It's modelled after the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md), with still some rough differences and missing properties in this **alpha release**.
+
+By default, It includes reference to `data` — which is automatically populated if you use the `getData()` function, and `state` which hold references to the global [`reactive()`] object.
+
+It automatically causes the component to be suspended if the `getData()`, `getMeta()` and `onEnter()` functions are asynchronous.
+
+```vue
+
+
{data.message}
+
+
+
+```
+
+### Execution order
+
+This graph illustrates the execution order to expect from route context initialization.
+
+```
+context.js default function export
+└─ getData() function export
+ └─ getMeta() function export
+ └─ onEnter() function export
+ └─ Route module
+```
+
+First the `default` function export from `context.js` (if present) is executed. This is where you can manually feed global server data into your application by populating the global Valtio state (the route context's `state` property, which is automatically hydrated on the client.
+
+Then `getData()` runs — which populates the route context's `data` property, and is also automatically hydrated on the client. Then `getMeta()`, which populates the route context's `head` property. Then `onEnter()`, and finally your route component.
diff --git a/docs/vue/route-enter.md b/docs/vue/route-enter.md
new file mode 100644
index 0000000..f40271b
--- /dev/null
+++ b/docs/vue/route-enter.md
@@ -0,0 +1,27 @@
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Universal Route Enter Event
+
+### `onEnter(ctx)`
+
+If a route module exports a `onEnter()` function, it's executed before the route renders, both in SSR and client-side navigation. That is, the first time a route render on the server, onEnter() runs on the server. Then, since it already ran on the server, it doesn't run again on the client for that first route. But if you navigate to another route on the client using ``, it runs normally as you'd expect.
+
+It receives the [universal route context][route-context] as first parameter, so you can make changes to `data`, `meta` and `state` if needed.
+
+[route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md
+
+```jsx
+export function onEnter (ctx) {
+ if (ctx.server?.underPressure) {
+ ctx.clientOnly = true
+ }
+}
+
+export function Index () {
+ return
No pre-rendered HTML sent to the browser.
+}
+```
+
+The example demonstrates how to turn off SSR and downgrade to CSR-only, assuming you have a `pressureHandler` configured in [`underpressure`](https://github.com/fastify/under-pressure) to set a `underPressure` flag on your server instance.
diff --git a/docs/vue/route-layouts.md b/docs/vue/route-layouts.md
new file mode 100644
index 0000000..a5fbf0c
--- /dev/null
+++ b/docs/vue/route-layouts.md
@@ -0,0 +1,56 @@
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Route Layouts
+
+Fastify DX will automatically load layouts from the `layouts/` folder. By default, `/dx:layouts/default.vue` is used — that is, if a project is missing a `layouts/defaults.vue` file, the one provided by Fastify DX is used instead.
+
+See the section on [Virtual Modules](https://github.com/fastify/fastify-dx/blob/main/docs/vue/virtual-modules.md) to learn more about this.
+
+You assign a layout to a route by exporting `layout`.
+
+See [`pages/using-auth.vue`](https://github.com/fastify/fastify-dx/blob/main/starters/vue/pages/using-auth.vue) in the starter template:
+
+```js
+export const layout = 'auth'
+```
+
+That'll will cause the route to be wrapped in the layout component exported by [`layouts/auth.vue`](https://github.com/fastify/fastify-dx/blob/main/starters/vue/layouts/auth.vue):
+
+```vue
+
+
+
+
This route needs authentication.
+
+
+
+
+
+
+
+```
+
+Note that like routes, it has access to `useRouteContext()`.
+
+
+
+
+
+
+
diff --git a/docs/vue/routing-config.md b/docs/vue/routing-config.md
new file mode 100644
index 0000000..44d0f84
--- /dev/null
+++ b/docs/vue/routing-config.md
@@ -0,0 +1,49 @@
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Routing Configuration
+
+By default, routes are loaded from the `/pages` folder, where `` refers to the `root` setting in `vite.config.js`. The route paths are **dynamically inferred from the directory structure**, very much like Next.js and Nuxt.js.
+
+### Dynamic parameters
+
+Dynamic route parameters follow the [Next.js convention](https://nextjs.org/docs/basic-features/pages#pages-with-dynamic-routes) (`[param]`), but that can be overriden by using the `paramPattern` plugin option. For example, this configuration switches the param pattern to the [Remix convention](https://remix.run/docs/en/v1/guides/routing#dynamic-segments) (`$param`).
+
+```js
+// ...
+const plugins = [
+ // ...
+ viteVueFastifyDX({ paramPattern: /\$(\w+)/ }),
+]
+```
+
+### Routes location
+
+You can also change the glob pattern used to determine where to route modules from.
+
+Since this setting is passed to [Vite's glob importers](https://vitejs.dev/guide/features.html#glob-import), the value needs to be a string:
+
+```js
+// ...
+const plugins = [
+ // ...
+ viteVueFastifyDX({ globPattern: '/views/**/*.vue' }),
+]
+```
+
+### View modules
+
+You also can export a `path` constant from your route modules, in which case its value will be used to **override the dynamically inferred paths from the directory structure**.
+
+Additionally, [**you can provide your own routes**](https://github.com/fastify/fastify-dx/tree/dev/packages/fastify-dx-vue#dxroutesjs).
+
+```jsx
+
+
Route with path export
+
+
+
+```
diff --git a/docs/vue/virtual-modules.md b/docs/vue/virtual-modules.md
new file mode 100644
index 0000000..2f79d4c
--- /dev/null
+++ b/docs/vue/virtual-modules.md
@@ -0,0 +1,191 @@
+
+**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**
+
+
+
+## Virtual Modules
+
+**Fastify DX** relies on [virtual modules](https://github.com/rollup/plugins/tree/master/packages/virtual) to save your project from having too many boilerplate files. Virtual modules are a [Rollup](https://rollupjs.org/guide/en/) feature exposed and fully supported by [Vite](https://vitejs.dev/). When you see imports that start with `/dx:`, you know a Fastify DX virtual module is being used.
+
+Fastify DX virtual modules are **fully ejectable**. For instance, the starter template relies on the `/dx:root.vue` virtual module to provide the Vue shell of your application. If you copy the `root.vue` file [from the fastify-dx-vue package](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/root.vue) and place it your Vite project root, **that copy of the file is used instead**. In fact, the starter template already comes with a custom `root.vue` of its own to include UnoCSS.
+
+Aside from `root.vue`, the starter template comes with two other virtual modules already ejected and part of the local project — `context.js` and `layouts/default.vue`. If you don't need to customize them, you can safely removed them from your project.
+
+### `/dx:root.vue`
+
+This is the root Vue component. It's used internally by `/dx:create.js` and provided as part of the starter template. You can use this file to add a common layout to all routes, and also to extend your Vue app by exporting a `configure()` function. For example, Fastify DX for Vue comes with a SSR-safe, global state based on a simple `reactive()` object — but if you want to use Pinia, you could set it up as follows:
+
+```js
+import { createPinia } from 'pinia'
+
+export function configure (app) {
+ const pinia = createPinia()
+ app.use(pinia)
+}
+```
+
+> Alternatively, you could eject the full `create.js` virtual module where `app` is fully defined.
+
+The version provided as part of the starter template includes [UnoCSS](https://github.com/unocss/unocss)'s own virtual module import, necessary to enable its CSS engine.
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+Note that a top-level `` wrapper is necessary because Fastify DX has code-splitting enabled at the route-level. You can opt out of code-splitting by providing your own `routes.js` file, but that's very unlikely to be ever required for any reason.
+
+### `/dx:routes.js`
+
+Fastify DX has **code-splitting** out of the box. It does that by eagerly loading all route data on the server, and then hydrating any missing metadata on the client. That's why the routes module default export is conditioned to `import.meta.env.SSR`, and different helper functions are called for each rendering environment.
+
+```js
+export default import.meta.env.SSR
+ ? createRoutes(import.meta.globEager('$globPattern'))
+ : hydrateRoutes(import.meta.glob('$globPattern'))
+```
+
+See [the full file](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/routes.js) for the `createRoutes()` and `hydrateRoutes()` definitions.
+
+If you want to use your own custom routes list, you must eject this file as-is and replace the glob imports with your own routes list:
+
+```js
+const routes = [
+ {
+ path: '/',
+ component: () => import('/custom/index.vue'),
+ }
+]
+
+export default import.meta.env.SSR
+ ? createRoutes(routes)
+ : hydrateRoutes(routes)
+````
+
+### `/dx:core.js`
+
+Implements `useRouteContext()` and `createBeforeEachHandler()`, used by `core.js`.
+
+`DXApp` is imported by `root.vue` and encapsulates Fastify DX's route component API.
+
+> Vue Router's [nested routes](https://router.vuejs.org/guide/essentials/nested-routes.html) aren't supported yet.
+
+See its full definition [here](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/core.js).
+
+### `/dx:create.js`
+
+This virtual module creates your root Vue component.
+
+This is where `root.vue` is imported.
+
+You'll rarely need to customize this file.
+
+```js
+import { createApp, createSSRApp, reactive, ref } from 'vue'
+import { createRouter } from 'vue-router'
+import {
+ isServer,
+ createHistory,
+ serverRouteContext,
+ routeLayout,
+ createBeforeEachHandler,
+} from '/dx:core.js'
+import root from '/dx:root.vue'
+
+export default async function create (ctx) {
+ const { routes, ctxHydration } = ctx
+
+ const instance = ctxHydration.clientOnly
+ ? createApp(root)
+ : createSSRApp(root)
+
+ const history = createHistory()
+ const router = createRouter({ history, routes })
+ const layoutRef = ref(ctxHydration.layout ?? 'default')
+
+ instance.provide(routeLayout, layoutRef)
+ ctxHydration.state = reactive(ctxHydration.state)
+
+ if (isServer) {
+ instance.provide(serverRouteContext, ctxHydration)
+ } else {
+ router.beforeEach(createBeforeEachHandler(ctx, layoutRef))
+ }
+
+ instance.use(router)
+
+ if (ctx.url) {
+ router.push(ctx.url)
+ await router.isReady()
+ }
+
+ return { instance, ctx, router }
+}
+```
+
+What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/create.js).
+
+### `/dx:layout.vue`
+
+This is responsible for loading **layout components**. It's part of `root.vue` by default. If a project has no `layouts/default.vue` file, the default one from Fastify DX is used. This virtual module works in conjunction with the `/dx:layouts/` virtual module which provides exports from the `/layouts` folder.
+
+You'll rarely need to customize this file.
+
+```vue
+
+
+
+
+
+
+
+```
+
+What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/layout.vue).
+
+### `/dx:mount.js`
+
+This is the file `index.html` links to by default. It sets up the application with an `unihead` instance for head management, the initial route context, and provides the conditional mounting logic to defer to CSR-only if `clientOnly` is enabled.
+
+You'll rarely need to customize this file.
+
+[See the full file](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/mount.js) for the `mount()` function definition.
From 3e533d8cf01795cc8003ace84373193e45aca6bf Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Sun, 19 Jun 2022 23:52:49 -0300
Subject: [PATCH 07/14] Update basic-setup.md
---
docs/vue/basic-setup.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/vue/basic-setup.md b/docs/vue/basic-setup.md
index 02c8250..faab980 100644
--- a/docs/vue/basic-setup.md
+++ b/docs/vue/basic-setup.md
@@ -35,7 +35,7 @@ await server.listen(3000)
The starter template's [`vite.config.js`](https://github.com/fastify/fastify-dx/blob/main/starters/vue/vite.config.js) file:
```js
-import viteReact from '@vitejs/plugin-react'
+import viteVue from '@vitejs/plugin-vue'
import viteVueFastifyDX from 'fastify-dx-vue/plugin'
import unocss from 'unocss/vite'
@@ -43,7 +43,7 @@ const path = fileURLToPath(import.meta.url)
const root = join(dirname(path), 'client')
const plugins = [
- viteReact(),
+ viteVue(),
viteVueFastifyDX(),
unocss()
]
From 12ebeb370edc494089a6526fd01193c556f7e037 Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Mon, 20 Jun 2022 00:16:07 -0300
Subject: [PATCH 08/14] Update project-structure.md
---
docs/vue/project-structure.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/vue/project-structure.md b/docs/vue/project-structure.md
index f8d6332..b55f7a9 100644
--- a/docs/vue/project-structure.md
+++ b/docs/vue/project-structure.md
@@ -34,7 +34,7 @@ In this case, placing a file with the same name as the registered virtual module
[virtual-modules]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/virtual-modules.md
-The `server.js` file is your application entry point. It's the file that runs everything. It boots a Fastify server configured with [**fastify-vite**](https://github.com/fastify/fastify-vite) and **Fastify DX for React** as a renderer adapter to **fastify-vite**.
+The `server.js` file is your application entry point. It's the file that runs everything. It boots a Fastify server configured with [**fastify-vite**](https://github.com/fastify/fastify-vite) and **Fastify DX for Vue** as a renderer adapter to **fastify-vite**.
The `client/index.js` file is your Vite server entry point, it's the file that provides your client bundle (which runs in the Vite-enriched environment) to the Node.js environment where Fastify runs.
@@ -54,7 +54,7 @@ The `client/pages/` directory contains your route modules, whose paths are dynam
[routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/routing-config.md
-The `client/layouts/` directory contains your route layout modules, which can be associated to any route. By default, `layouts/default.jsx` is used, but if you don't need to do any modifications on that file, you can safely removed as it's provided by Fastify DX in that case. The starter template also comes with `layouts/auth.jsx`, to demonstrate a more advanced use of layouts.
+The `client/layouts/` directory contains your route layout modules, which can be associated to any route. By default, `layouts/default.vue` is used, but if you don't need to do any modifications on that file, you can safely removed as it's provided by Fastify DX in that case. The starter template also comes with `layouts/auth.vue`, to demonstrate a more advanced use of layouts.
[routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/routing-config.md
From b596badfc793b094e0778201540605ac074b8dfc Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Mon, 20 Jun 2022 00:36:47 -0300
Subject: [PATCH 09/14] Update route-context.md
---
docs/vue/route-context.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/vue/route-context.md b/docs/vue/route-context.md
index 358aabd..a34db77 100644
--- a/docs/vue/route-context.md
+++ b/docs/vue/route-context.md
@@ -63,7 +63,7 @@ See the [full example](https://github.com/fastify/fastify-dx/blob/main/starters/
This hook can be used in any Vue component to retrieve a reference to the current route context. It's modelled after the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md), with still some rough differences and missing properties in this **alpha release**.
-By default, It includes reference to `data` — which is automatically populated if you use the `getData()` function, and `state` which hold references to the global [`reactive()`] object.
+By default, It includes reference to `data` — which is automatically populated if you use the `getData()` function, and `state` which hold references to the global [`reactive()`](https://vuejs.org/api/reactivity-core.html#reactive) object.
It automatically causes the component to be suspended if the `getData()`, `getMeta()` and `onEnter()` functions are asynchronous.
@@ -90,6 +90,6 @@ context.js default function export
└─ Route module
```
-First the `default` function export from `context.js` (if present) is executed. This is where you can manually feed global server data into your application by populating the global Valtio state (the route context's `state` property, which is automatically hydrated on the client.
+First the `default` function export from `context.js` (if present) is executed. This is where you can manually feed global server data into your application by populating the global state (the route context's `state` property, which is automatically hydrated on the client.
Then `getData()` runs — which populates the route context's `data` property, and is also automatically hydrated on the client. Then `getMeta()`, which populates the route context's `head` property. Then `onEnter()`, and finally your route component.
From d6e89fdab09342ef4cabfb690f1fe603a3bf1421 Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Mon, 20 Jun 2022 00:37:24 -0300
Subject: [PATCH 10/14] Update route-enter.md
---
docs/vue/route-enter.md | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/docs/vue/route-enter.md b/docs/vue/route-enter.md
index f40271b..f32291b 100644
--- a/docs/vue/route-enter.md
+++ b/docs/vue/route-enter.md
@@ -12,16 +12,18 @@ It receives the [universal route context][route-context] as first parameter, so
[route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md
-```jsx
+```vue
+
+
No pre-rendered HTML sent to the browser.
+
+
+
```
The example demonstrates how to turn off SSR and downgrade to CSR-only, assuming you have a `pressureHandler` configured in [`underpressure`](https://github.com/fastify/under-pressure) to set a `underPressure` flag on your server instance.
From adf8ef66af139d49174d73d6360d21cc033d38fa Mon Sep 17 00:00:00 2001
From: Jonas Galvez
Date: Mon, 20 Jun 2022 00:38:33 -0300
Subject: [PATCH 11/14] Update route-enter.md
---
docs/vue/route-enter.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/vue/route-enter.md b/docs/vue/route-enter.md
index f32291b..ec20f27 100644
--- a/docs/vue/route-enter.md
+++ b/docs/vue/route-enter.md
@@ -12,7 +12,7 @@ It receives the [universal route context][route-context] as first parameter, so
[route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/vue/route-context.md
-```vue
+```html