From 350d66c543478e18167dd29e17d0629b268dc1c1 Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Wed, 22 Jun 2022 23:20:08 -0300 Subject: [PATCH 01/20] wip svelte --- packages/fastify-dx-svelte/.eslintrc | 32 +++ packages/fastify-dx-svelte/README.md | 90 +++++++++ packages/fastify-dx-svelte/index.js | 186 ++++++++++++++++++ packages/fastify-dx-svelte/package.json | 45 ++++- packages/fastify-dx-svelte/plugin.cjs | 107 ++++++++++ packages/fastify-dx-svelte/server/context.js | 65 ++++++ packages/fastify-dx-svelte/server/stream.js | 7 + packages/fastify-dx-svelte/virtual/context.js | 4 + packages/fastify-dx-svelte/virtual/core.js | 143 ++++++++++++++ packages/fastify-dx-svelte/virtual/layouts.js | 14 ++ .../virtual/layouts/default.jsx | 12 ++ packages/fastify-dx-svelte/virtual/mount.js | 43 ++++ .../fastify-dx-svelte/virtual/resource.js | 68 +++++++ .../fastify-dx-svelte/virtual/root.svelte | 24 +++ .../fastify-dx-svelte/virtual/route.svelte | 70 +++++++ packages/fastify-dx-svelte/virtual/routes.js | 119 +++++++++++ starters/svelte/.eslintignore | 1 + starters/svelte/.eslintrc | 31 +++ starters/svelte/client/assets/logo.svg | 84 ++++++++ starters/svelte/client/base.css | 56 ++++++ starters/svelte/client/context.js | 36 ++++ starters/svelte/client/core.js | 25 +++ starters/svelte/client/index.html | 14 ++ starters/svelte/client/index.js | 9 + starters/svelte/client/layouts/auth.jsx | 25 +++ starters/svelte/client/layouts/default.jsx | 9 + .../svelte/client/pages/client-only.svelte | 21 ++ starters/svelte/client/pages/index.svelte | 33 ++++ .../svelte/client/pages/server-only.svelte | 15 ++ .../svelte/client/pages/using-auth.svelte_ | 27 +++ .../svelte/client/pages/using-data.svelte | 46 +++++ .../svelte/client/pages/using-store.svelte_ | 27 +++ starters/svelte/client/root.svelte | 24 +++ starters/svelte/package.json | 44 +++++ starters/svelte/postcss.config.cjs | 9 + starters/svelte/server.js | 32 +++ starters/svelte/vite.config.js | 24 +++ 37 files changed, 1618 insertions(+), 3 deletions(-) create mode 100644 packages/fastify-dx-svelte/.eslintrc create mode 100644 packages/fastify-dx-svelte/README.md create mode 100644 packages/fastify-dx-svelte/plugin.cjs create mode 100644 packages/fastify-dx-svelte/server/context.js create mode 100644 packages/fastify-dx-svelte/server/stream.js create mode 100644 packages/fastify-dx-svelte/virtual/context.js create mode 100644 packages/fastify-dx-svelte/virtual/core.js create mode 100644 packages/fastify-dx-svelte/virtual/layouts.js create mode 100644 packages/fastify-dx-svelte/virtual/layouts/default.jsx create mode 100644 packages/fastify-dx-svelte/virtual/mount.js create mode 100644 packages/fastify-dx-svelte/virtual/resource.js create mode 100644 packages/fastify-dx-svelte/virtual/root.svelte create mode 100644 packages/fastify-dx-svelte/virtual/route.svelte create mode 100644 packages/fastify-dx-svelte/virtual/routes.js create mode 100644 starters/svelte/.eslintignore create mode 100644 starters/svelte/.eslintrc create mode 100644 starters/svelte/client/assets/logo.svg create mode 100644 starters/svelte/client/base.css create mode 100644 starters/svelte/client/context.js create mode 100644 starters/svelte/client/core.js create mode 100644 starters/svelte/client/index.html create mode 100644 starters/svelte/client/index.js create mode 100644 starters/svelte/client/layouts/auth.jsx create mode 100644 starters/svelte/client/layouts/default.jsx create mode 100644 starters/svelte/client/pages/client-only.svelte create mode 100644 starters/svelte/client/pages/index.svelte create mode 100644 starters/svelte/client/pages/server-only.svelte create mode 100644 starters/svelte/client/pages/using-auth.svelte_ create mode 100644 starters/svelte/client/pages/using-data.svelte create mode 100644 starters/svelte/client/pages/using-store.svelte_ create mode 100644 starters/svelte/client/root.svelte create mode 100644 starters/svelte/package.json create mode 100644 starters/svelte/postcss.config.cjs create mode 100644 starters/svelte/server.js create mode 100644 starters/svelte/vite.config.js diff --git a/packages/fastify-dx-svelte/.eslintrc b/packages/fastify-dx-svelte/.eslintrc new file mode 100644 index 0000000..2ade438 --- /dev/null +++ b/packages/fastify-dx-svelte/.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-svelte/README.md b/packages/fastify-dx-svelte/README.md new file mode 100644 index 0000000..8ae8e6f --- /dev/null +++ b/packages/fastify-dx-svelte/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-svelte/index.js b/packages/fastify-dx-svelte/index.js index e69de29..94b15c1 100644 --- a/packages/fastify-dx-svelte/index.js +++ b/packages/fastify-dx-svelte/index.js @@ -0,0 +1,186 @@ +// 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() + const style = ( + app.style?.code && ( + `` + ) + ) || '' + + // Create readable stream with prepended and appended chunks + const readable = Readable.from(generateHtmlStream({ + body: app.html, + head: headTemplate({ + ...context, + style, + 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, Root }) { + // 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 && Root.render({ + routes, + routeMap, + ctxHydration: req.route, + url: req.url, + }) + console.log('app', app) + // 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, 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) { + console.log('route', route) + 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-svelte/package.json b/packages/fastify-dx-svelte/package.json index 4af29c5..db63250 100644 --- a/packages/fastify-dx-svelte/package.json +++ b/packages/fastify-dx-svelte/package.json @@ -1,6 +1,45 @@ { + "scripts": { + "lint": "eslint . --ext .js,.jsx --fix" + }, + "type": "module", + "main": "index.js", "name": "fastify-dx-svelte", - "version": "0.0.0", - "files": ["index.js"], - "license": "MIT" + "version": "0.0.1", + "files": [ + "virtual/root.svelte", + "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": { + "svelte-routing": "^1.6.0", + "svelte-loadable": "^2.0.1", + "devalue": "^2.0.1", + "unihead": "^0.0.6" + }, + "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-svelte/plugin.cjs b/packages/fastify-dx-svelte/plugin.cjs new file mode 100644 index 0000000..6829d54 --- /dev/null +++ b/packages/fastify-dx-svelte/plugin.cjs @@ -0,0 +1,107 @@ +const { readFileSync, existsSync } = require('fs') +const { dirname, join, resolve } = require('path') +const { fileURLToPath } = require('url') + +function viteSvelteFastifyDX (config = {}) { + const prefix = /^\/?dx:/ + const routing = Object.assign({ + globPattern: '/pages/**/*.svelte', + paramPattern: /\[(\w+)\]/, + }, config) + const virtualRoot = resolve(__dirname, 'virtual') + const virtualModules = [ + 'mount.js', + 'resource.js', + 'routes.js', + 'layouts.js', + 'root.svelte', + 'route.svelte', + 'layouts/', + 'context.js', + 'core.js' + ] + 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 = viteSvelteFastifyDX diff --git a/packages/fastify-dx-svelte/server/context.js b/packages/fastify-dx-svelte/server/context.js new file mode 100644 index 0000000..fab9a2e --- /dev/null +++ b/packages/fastify-dx-svelte/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-svelte/server/stream.js b/packages/fastify-dx-svelte/server/stream.js new file mode 100644 index 0000000..8b97b8d --- /dev/null +++ b/packages/fastify-dx-svelte/server/stream.js @@ -0,0 +1,7 @@ + +// Helper function to prepend and append chunks the body stream +export async function * generateHtmlStream ({ head, body, footer }) { + yield head + yield body + yield footer +} diff --git a/packages/fastify-dx-svelte/virtual/context.js b/packages/fastify-dx-svelte/virtual/context.js new file mode 100644 index 0000000..1e605f5 --- /dev/null +++ b/packages/fastify-dx-svelte/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-svelte/virtual/core.js b/packages/fastify-dx-svelte/virtual/core.js new file mode 100644 index 0000000..da34ae6 --- /dev/null +++ b/packages/fastify-dx-svelte/virtual/core.js @@ -0,0 +1,143 @@ +// https://github.com/ItalyPaleAle/svelte-spa-router +// https://github.com/AlexxNB/tinro#api + +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-svelte/virtual/layouts.js b/packages/fastify-dx-svelte/virtual/layouts.js new file mode 100644 index 0000000..3a065ab --- /dev/null +++ b/packages/fastify-dx-svelte/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-svelte/virtual/layouts/default.jsx b/packages/fastify-dx-svelte/virtual/layouts/default.jsx new file mode 100644 index 0000000..aecd273 --- /dev/null +++ b/packages/fastify-dx-svelte/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-svelte/virtual/mount.js b/packages/fastify-dx-svelte/virtual/mount.js new file mode 100644 index 0000000..191ec46 --- /dev/null +++ b/packages/fastify-dx-svelte/virtual/mount.js @@ -0,0 +1,43 @@ +import Head from 'unihead/client' +import Root from '/dx:root.svelte' +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]), + ) + new Root({ + target: document.querySelector('main'), + props: { + head, + ctxHydration, + routes: window.routes, + routeMap, + }, + hydrate: !ctxHydration.clientOnly, + }) +} + +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-svelte/virtual/resource.js b/packages/fastify-dx-svelte/virtual/resource.js new file mode 100644 index 0000000..69d7310 --- /dev/null +++ b/packages/fastify-dx-svelte/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-svelte/virtual/root.svelte b/packages/fastify-dx-svelte/virtual/root.svelte new file mode 100644 index 0000000..80821c9 --- /dev/null +++ b/packages/fastify-dx-svelte/virtual/root.svelte @@ -0,0 +1,24 @@ + + + + {#each routes as { path, component }} + + + + {/each} + diff --git a/packages/fastify-dx-svelte/virtual/route.svelte b/packages/fastify-dx-svelte/virtual/route.svelte new file mode 100644 index 0000000..f00abc5 --- /dev/null +++ b/packages/fastify-dx-svelte/virtual/route.svelte @@ -0,0 +1,70 @@ + + +{#if isServer} + +{:else} +{#await promise}{:then} + +{/await} +{/if} diff --git a/packages/fastify-dx-svelte/virtual/routes.js b/packages/fastify-dx-svelte/virtual/routes.js new file mode 100644 index 0000000..617eec2 --- /dev/null +++ b/packages/fastify-dx-svelte/virtual/routes.js @@ -0,0 +1,119 @@ +/* global $paramPattern */ + +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, -7) + // 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 = () => 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/svelte/.eslintignore b/starters/svelte/.eslintignore new file mode 100644 index 0000000..53c37a1 --- /dev/null +++ b/starters/svelte/.eslintignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/starters/svelte/.eslintrc b/starters/svelte/.eslintrc new file mode 100644 index 0000000..be9e0ff --- /dev/null +++ b/starters/svelte/.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/svelte/client/assets/logo.svg b/starters/svelte/client/assets/logo.svg new file mode 100644 index 0000000..9f2f2fd --- /dev/null +++ b/starters/svelte/client/assets/logo.svg @@ -0,0 +1,84 @@ + +image/svg+xml + + + + + + + + + + + + + + + + diff --git a/starters/svelte/client/base.css b/starters/svelte/client/base.css new file mode 100644 index 0000000..fde0cfc --- /dev/null +++ b/starters/svelte/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/svelte/client/context.js b/starters/svelte/client/context.js new file mode 100644 index 0000000..1368eb8 --- /dev/null +++ b/starters/svelte/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/svelte/client/core.js b/starters/svelte/client/core.js new file mode 100644 index 0000000..52d9d98 --- /dev/null +++ b/starters/svelte/client/core.js @@ -0,0 +1,25 @@ +import { getContext } from 'svelte' + +export const routeContext = Symbol('routeContext') + +export function useRouteContext () { + return getContext(routeContext).routeContext +} + +export async function jsonDataFetch (path) { + const response = await fetch(`/-/data${path}`) + let data + let error + try { + data = await response.json() + } catch (err) { + error = err + } + if (data?.statusCode === 500) { + throw new Error(data.message) + } + if (error) { + throw error + } + return data +} diff --git a/starters/svelte/client/index.html b/starters/svelte/client/index.html new file mode 100644 index 0000000..dd27666 --- /dev/null +++ b/starters/svelte/client/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + +
+ + + diff --git a/starters/svelte/client/index.js b/starters/svelte/client/index.js new file mode 100644 index 0000000..ddcae37 --- /dev/null +++ b/starters/svelte/client/index.js @@ -0,0 +1,9 @@ +import Root from '/dx:root.svelte' +import routes from '/dx:routes.js' +import * as context from '/dx:context.js' + +export default { + Root, + routes, + context, +} diff --git a/starters/svelte/client/layouts/auth.jsx b/starters/svelte/client/layouts/auth.jsx new file mode 100644 index 0000000..517989a --- /dev/null +++ b/starters/svelte/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/svelte/client/layouts/default.jsx b/starters/svelte/client/layouts/default.jsx new file mode 100644 index 0000000..604b629 --- /dev/null +++ b/starters/svelte/client/layouts/default.jsx @@ -0,0 +1,9 @@ +import { Suspense } from 'react' + +export default function Default ({ children }) { + return ( + + {children} + + ) +} diff --git a/starters/svelte/client/pages/client-only.svelte b/starters/svelte/client/pages/client-only.svelte new file mode 100644 index 0000000..99ee045 --- /dev/null +++ b/starters/svelte/client/pages/client-only.svelte @@ -0,0 +1,21 @@ + + + + +

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/svelte/client/pages/index.svelte b/starters/svelte/client/pages/index.svelte new file mode 100644 index 0000000..3afe5f1 --- /dev/null +++ b/starters/svelte/client/pages/index.svelte @@ -0,0 +1,33 @@ + + + + + +

Welcome to Fastify DX for Svelte!

+
    +
  • /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/svelte/client/pages/server-only.svelte b/starters/svelte/client/pages/server-only.svelte new file mode 100644 index 0000000..cec05cc --- /dev/null +++ b/starters/svelte/client/pages/server-only.svelte @@ -0,0 +1,15 @@ + + + + +

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/svelte/client/pages/using-auth.svelte_ b/starters/svelte/client/pages/using-auth.svelte_ new file mode 100644 index 0000000..ec43ec6 --- /dev/null +++ b/starters/svelte/client/pages/using-auth.svelte_ @@ -0,0 +1,27 @@ + + + + +

Todo List — Using Store

+
    + {#each $todoList as item, i} +
  • {item}
  • + {/each} +
+
+ + +
+

+ 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.

diff --git a/starters/svelte/client/pages/using-data.svelte b/starters/svelte/client/pages/using-data.svelte new file mode 100644 index 0000000..045bd90 --- /dev/null +++ b/starters/svelte/client/pages/using-data.svelte @@ -0,0 +1,46 @@ + + + + +

Todo List — Using Data

+
    + {#each todoList as item, i} +
  • {item}
  • + {/each} +
+
+ + +
+

+ 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/svelte/client/pages/using-store.svelte_ b/starters/svelte/client/pages/using-store.svelte_ new file mode 100644 index 0000000..ec43ec6 --- /dev/null +++ b/starters/svelte/client/pages/using-store.svelte_ @@ -0,0 +1,27 @@ + + + + +

Todo List — Using Store

+
    + {#each $todoList as item, i} +
  • {item}
  • + {/each} +
+
+ + +
+

+ 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.

diff --git a/starters/svelte/client/root.svelte b/starters/svelte/client/root.svelte new file mode 100644 index 0000000..eac7091 --- /dev/null +++ b/starters/svelte/client/root.svelte @@ -0,0 +1,24 @@ + + + + {#each routes as { path, component }} + + + + {/each} + diff --git a/starters/svelte/package.json b/starters/svelte/package.json new file mode 100644 index 0000000..89d3dc2 --- /dev/null +++ b/starters/svelte/package.json @@ -0,0 +1,44 @@ +{ + "type": "module", + "scripts": { + "dev": "node server.js --dev", + "build": "npm run build:client && npm run build:server", + "serve": "node server.js", + "devinstall": "zx ../../devinstall.mjs svelte -- node server.js --dev", + "build:client": "vite build --outDir dist/client --ssrManifest", + "build:server": "vite build --outDir dist/server --ssr /index.js", + "lint": "eslint . --ext .js,.jsx --fix" + }, + "dependencies": { + "fastify-vite": "^3.0.0-beta.23", + "ky-universal": "^0.10.1", + "svelte-routing": "^1.6.0", + "svelte-loadable": "^2.0.1", + "devalue": "^2.0.1", + "unihead": "^0.0.6" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.16.0", + "@babel/preset-react": "^7.16.0", + "@sveltejs/vite-plugin-svelte": "^1.0.0-next.49", + "@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", + "postcss-preset-env": "^7.7.1", + "unocss": "^0.37.4", + "vite-plugin-blueprint": "^0.0.4" + }, + "devInstall": { + "local": { + "fastify-dx-svelte": "^0.0.1-alpha.0" + }, + "external": { + "fastify-vite": "^3.0.0-beta.23", + "ky-universal": "^0.10.1" + } + } +} \ No newline at end of file diff --git a/starters/svelte/postcss.config.cjs b/starters/svelte/postcss.config.cjs new file mode 100644 index 0000000..8b78078 --- /dev/null +++ b/starters/svelte/postcss.config.cjs @@ -0,0 +1,9 @@ +const postcssPresetEnv = require('postcss-preset-env') + +module.exports = { + plugins: [ + postcssPresetEnv({ + stage: 1, + }), + ] +} diff --git a/starters/svelte/server.js b/starters/svelte/server.js new file mode 100644 index 0000000..5802c9e --- /dev/null +++ b/starters/svelte/server.js @@ -0,0 +1,32 @@ +import Fastify from 'fastify' +import FastifyVite from 'fastify-vite' +import FastifyDXSvelte from 'fastify-dx-svelte' + +const server = Fastify() + +server.decorate('db', { + todoList: [ + 'Do laundry', + 'Respond to emails', + 'Write report', + ] +}) + +server.put('/api/todo/items', (req, reply) => { + server.db.todoList.push(req.body.item) + reply.send({ ok: true }) +}) + +server.delete('/api/todo/items', (req, reply) => { + server.db.todoList.splice(req.body.index, 1) + reply.send({ ok: true }) +}) + +await server.register(FastifyVite, { + root: import.meta.url, + renderer: FastifyDXSvelte, +}) + +await server.vite.ready() + +await server.listen(3000) diff --git a/starters/svelte/vite.config.js b/starters/svelte/vite.config.js new file mode 100644 index 0000000..412b720 --- /dev/null +++ b/starters/svelte/vite.config.js @@ -0,0 +1,24 @@ +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' + +import { svelte as viteSvelte } from '@sveltejs/vite-plugin-svelte' +import viteSvelteFastifyDX from 'fastify-dx-svelte/plugin' +import unocss from 'unocss/vite' + +const path = fileURLToPath(import.meta.url) + +const root = join(dirname(path), 'client') +const plugins = [ + viteSvelte({ + compilerOptions: { + hydratable: true, + } + }), + viteSvelteFastifyDX(), + unocss() +] + +export default { + root, + plugins, +} From 0ed6301d7d5f9ca9552aa9d74e4692825d23e60f Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Thu, 23 Jun 2022 11:15:41 -0300 Subject: [PATCH 02/20] wip svelte --- packages/fastify-dx-svelte/index.js | 1 - packages/fastify-dx-svelte/package.json | 1 + packages/fastify-dx-svelte/virtual/layouts.js | 12 ++- .../virtual/layouts/default.jsx | 12 --- .../virtual/layouts/default.svelte | 8 ++ .../fastify-dx-svelte/virtual/route.svelte | 87 +++++++++++-------- starters/svelte/client/context.js | 6 +- starters/svelte/client/core.js | 5 +- starters/svelte/client/layouts/auth.jsx | 25 ------ starters/svelte/client/layouts/auth.svelte | 15 ++++ starters/svelte/client/layouts/default.jsx | 9 -- starters/svelte/client/layouts/default.svelte | 8 ++ .../{using-auth.svelte_ => using-auth.svelte} | 17 +++- ...using-store.svelte_ => using-store.svelte} | 15 +++- starters/svelte/client/root.svelte | 5 ++ starters/svelte/package.json | 1 + 16 files changed, 128 insertions(+), 99 deletions(-) delete mode 100644 packages/fastify-dx-svelte/virtual/layouts/default.jsx create mode 100644 packages/fastify-dx-svelte/virtual/layouts/default.svelte delete mode 100644 starters/svelte/client/layouts/auth.jsx create mode 100644 starters/svelte/client/layouts/auth.svelte delete mode 100644 starters/svelte/client/layouts/default.jsx create mode 100644 starters/svelte/client/layouts/default.svelte rename starters/svelte/client/pages/{using-auth.svelte_ => using-auth.svelte} (61%) rename starters/svelte/client/pages/{using-store.svelte_ => using-store.svelte} (63%) diff --git a/packages/fastify-dx-svelte/index.js b/packages/fastify-dx-svelte/index.js index 94b15c1..e9d5b74 100644 --- a/packages/fastify-dx-svelte/index.js +++ b/packages/fastify-dx-svelte/index.js @@ -108,7 +108,6 @@ export async function createRenderFunction ({ routes, Root }) { ctxHydration: req.route, url: req.url, }) - console.log('app', app) // 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, app } diff --git a/packages/fastify-dx-svelte/package.json b/packages/fastify-dx-svelte/package.json index db63250..80836af 100644 --- a/packages/fastify-dx-svelte/package.json +++ b/packages/fastify-dx-svelte/package.json @@ -28,6 +28,7 @@ "dependencies": { "svelte-routing": "^1.6.0", "svelte-loadable": "^2.0.1", + "sveltio": "^1.0.5", "devalue": "^2.0.1", "unihead": "^0.0.6" }, diff --git a/packages/fastify-dx-svelte/virtual/layouts.js b/packages/fastify-dx-svelte/virtual/layouts.js index 3a065ab..e188cc1 100644 --- a/packages/fastify-dx-svelte/virtual/layouts.js +++ b/packages/fastify-dx-svelte/virtual/layouts.js @@ -1,14 +1,12 @@ -import { lazy } from 'react' +import DefaultLayout from '/dx:layouts/default.svelte' -const DefaultLayout = () => import('/dx:layouts/default.jsx') +const appLayouts = import.meta.globEager('/layouts/*.svelte') -const appLayouts = import.meta.glob('/layouts/*.jsx') - -appLayouts['/layouts/default.jsx'] ??= DefaultLayout +appLayouts['/layouts/default.svelte'] ??= DefaultLayout export default Object.fromEntries( Object.keys(appLayouts).map((path) => { - const name = path.slice(9, -4) - return [name, lazy(appLayouts[path])] + const name = path.slice(9, -7) + return [name, appLayouts[path]] }), ) diff --git a/packages/fastify-dx-svelte/virtual/layouts/default.jsx b/packages/fastify-dx-svelte/virtual/layouts/default.jsx deleted file mode 100644 index aecd273..0000000 --- a/packages/fastify-dx-svelte/virtual/layouts/default.jsx +++ /dev/null @@ -1,12 +0,0 @@ -// 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-svelte/virtual/layouts/default.svelte b/packages/fastify-dx-svelte/virtual/layouts/default.svelte new file mode 100644 index 0000000..c63d36b --- /dev/null +++ b/packages/fastify-dx-svelte/virtual/layouts/default.svelte @@ -0,0 +1,8 @@ + + +
+ +
diff --git a/packages/fastify-dx-svelte/virtual/route.svelte b/packages/fastify-dx-svelte/virtual/route.svelte index f00abc5..64150e8 100644 --- a/packages/fastify-dx-svelte/virtual/route.svelte +++ b/packages/fastify-dx-svelte/virtual/route.svelte @@ -2,6 +2,8 @@ import { setContext } from 'svelte' import Loadable from 'svelte-loadable' import { routeContext, jsonDataFetch } from '/core.js' +import layouts from '/dx:layouts.js' + const isServer = import.meta.env.SSR setContext(routeContext, { @@ -10,61 +12,76 @@ setContext(routeContext, { } }) +export let state = null export let location = null export let component = null export let head export let ctx = null export let ctxHydration = null +ctx.actions = ctxHydration.actions + +if (isServer) { + ctx.layout = ctxHydration.layout ?? 'default' + ctx.data = ctxHydration.data + ctx.state = state +} + async function setup () { - if (isServer) { + ctx.state = state + if (ctxHydration.firstRender) { ctx.data = ctxHydration.data - ctx.state = ctxHydration.state - } else { - if (ctxHydration.firstRender) { - ctx.data = ctxHydration.data - ctx.state = ctxHydration.state - ctxHydration.firstRender = false - return - } - const { getMeta, getData, onEnter } = await ctx.loader() - if (getData) { - try { - const fullPath = `${location.pathname}${location.search}` - const updatedData = await jsonDataFetch(fullPath) - if (!ctx.data) { - ctx.data = {} - } - if (updatedData) { - Object.assign(ctx.data, updatedData) - } - ctx.error = null - } catch (error) { - ctx.error = error - } - } - if (getMeta) { - const updatedMeta = await getMeta(ctx) - if (updatedMeta) { - head.update(updatedMeta) + ctx.layout = ctxHydration.layout ?? 'default' + ctxHydration.firstRender = false + return + } + ctx.layout = ctx.layout || 'default' + console.log('ctx.layout->', ctx.layout) + const { getMeta, getData, onEnter } = await ctx.loader() + if (getData) { + try { + const fullPath = `${location.pathname}${location.search}` + const updatedData = await jsonDataFetch(fullPath) + if (!ctx.data) { + ctx.data = {} } - } - if (onEnter) { - const updatedData = await onEnter(ctx) if (updatedData) { Object.assign(ctx.data, updatedData) } + ctx.error = null + } catch (error) { + ctx.error = error + } + } + if (getMeta) { + const updatedMeta = await getMeta(ctx) + if (updatedMeta) { + head.update(updatedMeta) + } + } + if (onEnter) { + const updatedData = await onEnter(ctx) + if (updatedData) { + Object.assign(ctx.data, updatedData) } } } -let promise = setup() +let promise = !isServer && setup() + +console.log('layouts', layouts) +console.log('ctx.layout', ctx.layout) +console.log('layouts[ctx.layout]', layouts[ctx.layout]) {#if isServer} - + + + {:else} {#await promise}{:then} - + + + {/await} {/if} diff --git a/starters/svelte/client/context.js b/starters/svelte/client/context.js index 1368eb8..acb9cf7 100644 --- a/starters/svelte/client/context.js +++ b/starters/svelte/client/context.js @@ -6,7 +6,7 @@ export default (ctx) => { } } -export const _fetch = ky.extend({ +export const $fetch = ky.extend({ prefixUrl: 'http://localhost:3000' }) @@ -22,13 +22,13 @@ export const actions = { state.user.authenticated = true }, async addTodoItem (state, item) { - await _fetch.put('api/todo/items', { + await $fetch.put('api/todo/items', { json: { item }, }) state.todoList.push(item) }, async removeTodoItem (state, index) { - await _fetch.delete('api/todo/items', { + await $fetch.delete('api/todo/items', { json: { index }, }) state.todoList.splice(index, 1) diff --git a/starters/svelte/client/core.js b/starters/svelte/client/core.js index 52d9d98..9c267d7 100644 --- a/starters/svelte/client/core.js +++ b/starters/svelte/client/core.js @@ -1,9 +1,12 @@ import { getContext } from 'svelte' +import { useSnapshot } from 'sveltio' export const routeContext = Symbol('routeContext') export function useRouteContext () { - return getContext(routeContext).routeContext + const ctx = getContext(routeContext).routeContext + ctx.snapshot = useSnapshot(ctx.state) + return ctx } export async function jsonDataFetch (path) { diff --git a/starters/svelte/client/layouts/auth.jsx b/starters/svelte/client/layouts/auth.jsx deleted file mode 100644 index 517989a..0000000 --- a/starters/svelte/client/layouts/auth.jsx +++ /dev/null @@ -1,25 +0,0 @@ -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/svelte/client/layouts/auth.svelte b/starters/svelte/client/layouts/auth.svelte new file mode 100644 index 0000000..33a6a93 --- /dev/null +++ b/starters/svelte/client/layouts/auth.svelte @@ -0,0 +1,15 @@ + + +
+ {#if !$snapshot.user.authenticated} +

This route needs authentication.

+ + {:else} + + {/if} +
diff --git a/starters/svelte/client/layouts/default.jsx b/starters/svelte/client/layouts/default.jsx deleted file mode 100644 index 604b629..0000000 --- a/starters/svelte/client/layouts/default.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Suspense } from 'react' - -export default function Default ({ children }) { - return ( - - {children} - - ) -} diff --git a/starters/svelte/client/layouts/default.svelte b/starters/svelte/client/layouts/default.svelte new file mode 100644 index 0000000..c63d36b --- /dev/null +++ b/starters/svelte/client/layouts/default.svelte @@ -0,0 +1,8 @@ + + +
+ +
diff --git a/starters/svelte/client/pages/using-auth.svelte_ b/starters/svelte/client/pages/using-auth.svelte similarity index 61% rename from starters/svelte/client/pages/using-auth.svelte_ rename to starters/svelte/client/pages/using-auth.svelte index ec43ec6..78b0dc3 100644 --- a/starters/svelte/client/pages/using-auth.svelte_ +++ b/starters/svelte/client/pages/using-auth.svelte @@ -2,16 +2,27 @@ export let getMeta = () => { return { title: 'Todo List — Using Store' } } + +export let layout = 'auth'

Todo List — Using Store

    - {#each $todoList as item, i} + {#each $snapshot.todoList as item, i}
  • {item}
  • {/each}
diff --git a/starters/svelte/client/pages/using-store.svelte_ b/starters/svelte/client/pages/using-store.svelte similarity index 63% rename from starters/svelte/client/pages/using-store.svelte_ rename to starters/svelte/client/pages/using-store.svelte index ec43ec6..10ad6c5 100644 --- a/starters/svelte/client/pages/using-store.svelte_ +++ b/starters/svelte/client/pages/using-store.svelte @@ -5,13 +5,22 @@ export let getMeta = () => {

Todo List — Using Store

    - {#each $todoList as item, i} + {#each $snapshot.todoList as item, i}
  • {item}
  • {/each}
diff --git a/starters/svelte/client/root.svelte b/starters/svelte/client/root.svelte index eac7091..64a1f6e 100644 --- a/starters/svelte/client/root.svelte +++ b/starters/svelte/client/root.svelte @@ -1,5 +1,6 @@ @@ -16,6 +20,7 @@ export let ctxHydration diff --git a/starters/svelte/package.json b/starters/svelte/package.json index 89d3dc2..fcd6d34 100644 --- a/starters/svelte/package.json +++ b/starters/svelte/package.json @@ -14,6 +14,7 @@ "ky-universal": "^0.10.1", "svelte-routing": "^1.6.0", "svelte-loadable": "^2.0.1", + "sveltio": "^1.0.5", "devalue": "^2.0.1", "unihead": "^0.0.6" }, From 2207d54792e24063155be3b143b158caec683686 Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Thu, 23 Jun 2022 20:34:29 -0300 Subject: [PATCH 03/20] wip svelte --- docs/svelte/basic-setup.md | 58 ++++++ docs/svelte/data-prefetching.md | 36 ++++ docs/svelte/index.md | 94 +++++++++ docs/svelte/meta-tags.md | 33 +++ docs/svelte/project-structure.md | 63 ++++++ docs/svelte/rendering-modes.md | 69 +++++++ docs/svelte/route-context.md | 95 +++++++++ docs/svelte/route-enter.md | 29 +++ docs/svelte/route-layouts.md | 56 +++++ docs/svelte/routing-config.md | 49 +++++ docs/svelte/virtual-modules.md | 191 ++++++++++++++++++ packages/fastify-dx-svelte/README.md | 54 ++--- packages/fastify-dx-svelte/index.js | 8 +- packages/fastify-dx-svelte/package.json | 5 +- packages/fastify-dx-svelte/virtual/core.js | 153 ++------------ packages/fastify-dx-svelte/virtual/mount.js | 14 +- .../fastify-dx-svelte/virtual/root.svelte | 18 +- .../fastify-dx-svelte/virtual/route.svelte | 44 ++-- starters/svelte/client/core.js | 28 --- starters/svelte/client/pages/index.svelte | 2 +- starters/svelte/client/root.svelte | 17 +- starters/svelte/package.json | 1 - starters/vue/package.json | 2 +- 23 files changed, 871 insertions(+), 248 deletions(-) create mode 100644 docs/svelte/basic-setup.md create mode 100644 docs/svelte/data-prefetching.md create mode 100644 docs/svelte/index.md create mode 100644 docs/svelte/meta-tags.md create mode 100644 docs/svelte/project-structure.md create mode 100644 docs/svelte/rendering-modes.md create mode 100644 docs/svelte/route-context.md create mode 100644 docs/svelte/route-enter.md create mode 100644 docs/svelte/route-layouts.md create mode 100644 docs/svelte/routing-config.md create mode 100644 docs/svelte/virtual-modules.md delete mode 100644 starters/svelte/client/core.js diff --git a/docs/svelte/basic-setup.md b/docs/svelte/basic-setup.md new file mode 100644 index 0000000..faab980 --- /dev/null +++ b/docs/svelte/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 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 = [ + viteVue(), + 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/svelte/data-prefetching.md b/docs/svelte/data-prefetching.md new file mode 100644 index 0000000..6eae389 --- /dev/null +++ b/docs/svelte/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 + + + +``` diff --git a/docs/svelte/index.md b/docs/svelte/index.md new file mode 100644 index 0000000..ac3ddd0 --- /dev/null +++ b/docs/svelte/index.md @@ -0,0 +1,94 @@ +# 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-svelte/README.md#introduction) +- [**Quick Start**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md#quick-start) +- [**Package Scripts**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md#package-scripts) +- [**Basic Setup**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/basic-setup.md) +- [**Project Structure**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/project-structure.md) +- [**Rendering Modes**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/rendering-modes.md) +- [**Routing Configuration**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/routing-config.md) +- [**Data Prefetching**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/data-prefetching.md) +- [**Route Layouts**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-layouts.md) +- [**Route Context**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md) +- [**Route Enter Event**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-enter.md) +- [**Virtual Modules**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/virtual-modules.md) + +## Introduction + +**Fastify DX for Svelte** is a renderer adapter for [**fastify-vite**](https://github.com/fastify/fastify-vite). + +It lets you run and SSR (server-side render) **Vue 3 applications built with Vite** on [Fastify](https://fastify.io/), with a minimal and transparent **server-first approach** — everything starts with `server.js`, your actual Fastify server). + +It also provides a set of built-in utilities for ease of development and managing a universal JavaScript context (SSR to CSR), very much like **Nuxt.js**, **Next.js** and **Remix**. All **Fastify DX** framework adapters implement the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md) and have almost the same API, with only minimal differences due to specific framework APIs or idioms. + +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/), [Valtio](https://github.com/pmndrs/valtio) and [Svelte Routing](https://github.com/EmilTholin/svelte-routing). + +[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/vue-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**](https://github.com/fastify/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/svelte**](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte). If you have [`degit`](https://github.com/Rich-Harris/degit), run the following from a new directory: + +```bash +degit fastify/fastify-dx/starters/svelte +``` + +> **If you're starting a project from scratch**, you'll need these packages installed. +> +> ```bash +> npm i fastify fastify-vite fastify-dx-svelte -P +> npm i @sveltejs/vite-plugin-svelte -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/svelte/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. Svelte support is provided via [Sveltio](https://github.com/wobsoriano/sveltio) by [Robert Soriano](https://robsoriano.com/). + + +## 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/svelte/meta-tags.md b/docs/svelte/meta-tags.md new file mode 100644 index 0000000..aae4487 --- /dev/null +++ b/docs/svelte/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 ``, `<meta>` and `<link>` 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 +<template> + <p>Route with meta tags.</p> +</template> + +<script> +export function getMeta (ctx) { + return { + title: 'Route Title', + meta: [ + { name: 'twitter:title', value: 'Route Title' }, + ] + } +} +</script> +``` diff --git a/docs/svelte/project-structure.md b/docs/svelte/project-structure.md new file mode 100644 index 0000000..b55f7a9 --- /dev/null +++ b/docs/svelte/project-structure.md @@ -0,0 +1,63 @@ + +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +## 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 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. + +> 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 `<meta>` and `<link>` 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.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 + +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/svelte/rendering-modes.md b/docs/svelte/rendering-modes.md new file mode 100644 index 0000000..95bc1b6 --- /dev/null +++ b/docs/svelte/rendering-modes.md @@ -0,0 +1,69 @@ + +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +# 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 +<template> + <Message :secs="2" /> + <Message :secs="4" /> + <Message :secs="6" /> +</template> + +<script> +import Message from '/components/Message.vue' + +export const streaming = true + +export default { + components: { Message }, +} +</script> +``` + +[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 +<template> + <p>This route is rendered on the server only!</p> +</template> + +<script> +export const serverOnly = true +</script> +``` + +[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 `<!-- element -->` 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 +<template> + <p>This route is rendered on the client only!</p> +</template> + +<script> +export const clientOnly = true +</script> +``` + +[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/svelte/route-context.md b/docs/svelte/route-context.md new file mode 100644 index 0000000..a34db77 --- /dev/null +++ b/docs/svelte/route-context.md @@ -0,0 +1,95 @@ +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +## 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()`](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. + +```vue +<template> + <p>{data.message}</p> +</template> + +<script setup> +import { useRouteContext } from '/dx:core.js' +const { data } = useRouteContext() +</script> +``` + +### 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 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/svelte/route-enter.md b/docs/svelte/route-enter.md new file mode 100644 index 0000000..ec20f27 --- /dev/null +++ b/docs/svelte/route-enter.md @@ -0,0 +1,29 @@ +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +## 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 `<Link>`, 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 + +```html +<template> + <p>No pre-rendered HTML sent to the browser.</p> +</template> + +<script> +export function onEnter (ctx) { + if (ctx.server?.underPressure) { + ctx.clientOnly = true + } +} +</script> +``` + +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/svelte/route-layouts.md b/docs/svelte/route-layouts.md new file mode 100644 index 0000000..a5fbf0c --- /dev/null +++ b/docs/svelte/route-layouts.md @@ -0,0 +1,56 @@ +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +## 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 +<template> + <div class="contents"> + <template v-if="!state.user.authenticated"> + <p>This route needs authentication.</p> + <button @click="authenticate"> + Click this button to authenticate. + </button> + </template> + <slot v-else></slot> + </div> +</template> + +<script> +import { useRouteContext } from '/dx:core.js' + +export default { + setup () { + const { actions, state } = useRouteContext() + return { + state, + authenticate: () => actions.authenticate(state) + } + } +} +</script> +``` + +Note that like routes, it has access to `useRouteContext()`. + + + + + + + diff --git a/docs/svelte/routing-config.md b/docs/svelte/routing-config.md new file mode 100644 index 0000000..44d0f84 --- /dev/null +++ b/docs/svelte/routing-config.md @@ -0,0 +1,49 @@ +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +## Routing Configuration + +By default, routes are loaded from the `<project-root>/pages` folder, where `<project-root>` 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 +<template> + <p>Route with path export</p> +</template> + +<script> +export const path = '/my-page' +</script> +``` diff --git a/docs/svelte/virtual-modules.md b/docs/svelte/virtual-modules.md new file mode 100644 index 0000000..2f79d4c --- /dev/null +++ b/docs/svelte/virtual-modules.md @@ -0,0 +1,191 @@ + +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> + +<br> + +## 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 +<script> +import 'uno.css' +</script> + +<script setup> +import Layout from '/dx:layout.vue' +</script> + +<template> + <router-view v-slot="{ Component }"> + <Suspense> + <Layout> + <component + :is="Component" + :key="$route.path" + /> + </Layout> + </Suspense> + </router-view> +</template> + +``` + +Note that a top-level `<Suspense>` 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. + +<b>You'll rarely need to customize this file.</b> + +```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. + +<b>You'll rarely need to customize this file.</b> + +```vue +<template> + <component :is="layout"> + <slot /> + </component> +</template> + +<script> +import { defineAsyncComponent, inject } from 'vue' +import { routeLayout } from '/dx:core.js' + +const DefaultLayout = () => import('/dx:layouts/default.vue') +const appLayouts = import.meta.glob('/layouts/*.vue') + +appLayouts['/layouts/default.vue'] ??= DefaultLayout + +export default { + setup: () => ({ + layout: inject(routeLayout) + }), + components: Object.fromEntries( + Object.keys(appLayouts).map((path) => { + const name = path.slice(9, -4) + return [name, defineAsyncComponent(appLayouts[path])] + }) + ) +} +</script> +``` + +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. + +<b>You'll rarely need to customize this file.</b> + +[See the full file](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/mount.js) for the `mount()` function definition. diff --git a/packages/fastify-dx-svelte/README.md b/packages/fastify-dx-svelte/README.md index 8ae8e6f..ac3ddd0 100644 --- a/packages/fastify-dx-svelte/README.md +++ b/packages/fastify-dx-svelte/README.md @@ -1,31 +1,35 @@ -# 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) +# 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-svelte/README.md#introduction) +- [**Quick Start**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md#quick-start) +- [**Package Scripts**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md#package-scripts) +- [**Basic Setup**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/basic-setup.md) +- [**Project Structure**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/project-structure.md) +- [**Rendering Modes**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/rendering-modes.md) +- [**Routing Configuration**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/routing-config.md) +- [**Data Prefetching**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/data-prefetching.md) +- [**Route Layouts**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-layouts.md) +- [**Route Context**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md) +- [**Route Enter Event**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-enter.md) +- [**Virtual Modules**](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/virtual-modules.md) ## Introduction -**Fastify DX for React** is a renderer adapter for [**fastify-vite**](https://github.com/fastify/fastify-vite). +**Fastify DX for Svelte** 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 lets you run and SSR (server-side render) **Vue 3 applications built with Vite** on [Fastify](https://fastify.io/), with a minimal and transparent **server-first approach** — everything starts with `server.js`, your actual Fastify server). -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). +It also provides a set of built-in utilities for ease of development and managing a universal JavaScript context (SSR to CSR), very much like **Nuxt.js**, **Next.js** and **Remix**. All **Fastify DX** framework adapters implement the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md) and have almost the same API, with only minimal differences due to specific framework APIs or idioms. -[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/v0.0.1). +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/), [Valtio](https://github.com/pmndrs/valtio) and [Svelte Routing](https://github.com/EmilTholin/svelte-routing). + +[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/vue-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**](). +In this first alpha release it's still missing a test suite. The same is true for [**fastify-vite**](https://github.com/fastify/fastify-vite). It'll move into **beta** status when test suites are added to both packages. @@ -33,17 +37,17 @@ It'll move into **beta** status when test suites are added to both packages. 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: +Make a copy of [**starters/svelte**](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte). If you have [`degit`](https://github.com/Rich-Harris/degit), run the following from a new directory: ```bash -degit fastify/fastify-dx/starters/react +degit fastify/fastify-dx/starters/svelte ``` > **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 +> npm i fastify fastify-vite fastify-dx-svelte -P +> npm i @sveltejs/vite-plugin-svelte -D > ``` @@ -59,7 +63,7 @@ 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). +- A `pages/` folder with some [demo routes](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte/client/pages). - All configuration files. It also includes some _**opinionated**_ essentials: @@ -68,7 +72,7 @@ It also includes some _**opinionated**_ essentials: - [**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. +- [**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. Svelte support is provided via [Sveltio](https://github.com/wobsoriano/sveltio) by [Robert Soriano](https://robsoriano.com/). ## Package Scripts diff --git a/packages/fastify-dx-svelte/index.js b/packages/fastify-dx-svelte/index.js index e9d5b74..6fad2a2 100644 --- a/packages/fastify-dx-svelte/index.js +++ b/packages/fastify-dx-svelte/index.js @@ -103,10 +103,12 @@ export async function createRenderFunction ({ routes, Root }) { })) // Creates main React component with all the SSR context it needs const app = !req.route.clientOnly && Root.render({ - routes, - routeMap, - ctxHydration: req.route, url: req.url, + payload: { + routes, + routeMap, + serverRoute: req.route, + }, }) // 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 diff --git a/packages/fastify-dx-svelte/package.json b/packages/fastify-dx-svelte/package.json index 80836af..6169857 100644 --- a/packages/fastify-dx-svelte/package.json +++ b/packages/fastify-dx-svelte/package.json @@ -33,14 +33,11 @@ "unihead": "^0.0.6" }, "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" + "eslint-plugin-svelte3": "^4.0.0" } } \ No newline at end of file diff --git a/packages/fastify-dx-svelte/virtual/core.js b/packages/fastify-dx-svelte/virtual/core.js index da34ae6..9c267d7 100644 --- a/packages/fastify-dx-svelte/virtual/core.js +++ b/packages/fastify-dx-svelte/virtual/core.js @@ -1,143 +1,28 @@ -// https://github.com/ItalyPaleAle/svelte-spa-router -// https://github.com/AlexxNB/tinro#api +import { getContext } from 'svelte' +import { useSnapshot } from 'sveltio' -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 const routeContext = Symbol('routeContext') 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 ( - <Router location={url}> - <Routes>{ - routes.map(({ path, component: Component }) => - <Route - key={path} - path={path} - element={ - <DXRoute - head={head} - ctxHydration={ctxHydration} - ctx={routeMap[path]}> - <Component /> - </DXRoute> - } />, - ) - }</Routes> - </Router> - ) + const ctx = getContext(routeContext).routeContext + ctx.snapshot = useSnapshot(ctx.state) + return ctx } -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 ( - <RouteContext.Provider value={{ - ...ctx, - ...ctxHydration, - state: proxy(ctxHydration.state), - }}> - <Layout> - {children} - </Layout> - </RouteContext.Provider> - ) +export async function jsonDataFetch (path) { + const response = await fetch(`/-/data${path}`) + let data + let error + try { + data = await response.json() + } catch (err) { + error = err } - // 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 + if (data?.statusCode === 500) { + throw new Error(data.message) } - - 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 (error) { + throw error } - - 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 ( - <RouteContext.Provider value={{ - ...ctxHydration, - ...ctx, - state: proxy(ctxHydration.state), - }}> - <Layout> - {children} - </Layout> - </RouteContext.Provider> - ) + return data } diff --git a/packages/fastify-dx-svelte/virtual/mount.js b/packages/fastify-dx-svelte/virtual/mount.js index 191ec46..0d8f604 100644 --- a/packages/fastify-dx-svelte/virtual/mount.js +++ b/packages/fastify-dx-svelte/virtual/mount.js @@ -9,7 +9,7 @@ async function mount (target) { target = document.querySelector(target) } const context = await import('/dx:context.js') - const ctxHydration = await extendContext(window.route, context) + const serverRoute = await extendContext(window.route, context) const head = new Head(window.route.head, window.document) const resolvedRoutes = await routesPromise const routeMap = Object.fromEntries( @@ -18,12 +18,14 @@ async function mount (target) { new Root({ target: document.querySelector('main'), props: { - head, - ctxHydration, - routes: window.routes, - routeMap, + payload: { + head, + serverRoute, + routes: window.routes, + routeMap, + }, }, - hydrate: !ctxHydration.clientOnly, + hydrate: !serverRoute.clientOnly, }) } diff --git a/packages/fastify-dx-svelte/virtual/root.svelte b/packages/fastify-dx-svelte/virtual/root.svelte index 80821c9..126d02b 100644 --- a/packages/fastify-dx-svelte/virtual/root.svelte +++ b/packages/fastify-dx-svelte/virtual/root.svelte @@ -1,23 +1,21 @@ <script> -import 'uno.css' +import { proxy } from 'sveltio' import { Router, Route } from 'svelte-routing' -import DXRoute from '/core/route.svelte' +import DXRoute from '/dx:route.svelte' export let url = null -export let head -export let routes -export let routeMap -export let ctxHydration +export let payload + +let state = proxy(payload.serverRoute.state) </script> <Router url="{url}"> - {#each routes as { path, component }} + {#each payload.routes as { path, component }} <Route path="{path}" let:location> <DXRoute location={location} - head={head} - ctx={routeMap[path]} - ctxHydration={ctxHydration} + state={state} + payload={payload} component={component} /> </Route> {/each} diff --git a/packages/fastify-dx-svelte/virtual/route.svelte b/packages/fastify-dx-svelte/virtual/route.svelte index 64150e8..684d7bf 100644 --- a/packages/fastify-dx-svelte/virtual/route.svelte +++ b/packages/fastify-dx-svelte/virtual/route.svelte @@ -1,7 +1,7 @@ <script> import { setContext } from 'svelte' import Loadable from 'svelte-loadable' -import { routeContext, jsonDataFetch } from '/core.js' +import { routeContext, jsonDataFetch } from '/dx:core.js' import layouts from '/dx:layouts.js' const isServer = import.meta.env.SSR @@ -12,31 +12,31 @@ setContext(routeContext, { } }) -export let state = null -export let location = null -export let component = null -export let head -export let ctx = null -export let ctxHydration = null +export let path +export let component +export let payload +export let state +export let location -ctx.actions = ctxHydration.actions +let ctx = payload.routeMap[path] + +ctx.state = state +ctx.actions = payload.serverRoute.actions if (isServer) { - ctx.layout = ctxHydration.layout ?? 'default' - ctx.data = ctxHydration.data + ctx.layout = payload.serverRoute.layout ?? 'default' + ctx.data = payload.serverRoute.data ctx.state = state } async function setup () { - ctx.state = state - if (ctxHydration.firstRender) { - ctx.data = ctxHydration.data - ctx.layout = ctxHydration.layout ?? 'default' - ctxHydration.firstRender = false + if (payload.serverRoute.firstRender) { + ctx.data = payload.serverRoute.data + ctx.layout = payload.serverRoute.layout ?? 'default' + payload.serverRoute.firstRender = false return } - ctx.layout = ctx.layout || 'default' - console.log('ctx.layout->', ctx.layout) + ctx.layout = ctx.layout ?? 'default' const { getMeta, getData, onEnter } = await ctx.loader() if (getData) { try { @@ -56,7 +56,7 @@ async function setup () { if (getMeta) { const updatedMeta = await getMeta(ctx) if (updatedMeta) { - head.update(updatedMeta) + payload.head.update(updatedMeta) } } if (onEnter) { @@ -67,11 +67,7 @@ async function setup () { } } -let promise = !isServer && setup() - -console.log('layouts', layouts) -console.log('ctx.layout', ctx.layout) -console.log('layouts[ctx.layout]', layouts[ctx.layout]) +let setupClientRouteContext = !isServer && setup() </script> {#if isServer} @@ -79,7 +75,7 @@ console.log('layouts[ctx.layout]', layouts[ctx.layout]) <svelte:component this={component} /> </svelte:component> {:else} -{#await promise}{:then} +{#await setupClientRouteContext}{:then} <svelte:component this={layouts[ctx.layout].default}> <Loadable loader={component} /> </svelte:component> diff --git a/starters/svelte/client/core.js b/starters/svelte/client/core.js deleted file mode 100644 index 9c267d7..0000000 --- a/starters/svelte/client/core.js +++ /dev/null @@ -1,28 +0,0 @@ -import { getContext } from 'svelte' -import { useSnapshot } from 'sveltio' - -export const routeContext = Symbol('routeContext') - -export function useRouteContext () { - const ctx = getContext(routeContext).routeContext - ctx.snapshot = useSnapshot(ctx.state) - return ctx -} - -export async function jsonDataFetch (path) { - const response = await fetch(`/-/data${path}`) - let data - let error - try { - data = await response.json() - } catch (err) { - error = err - } - if (data?.statusCode === 500) { - throw new Error(data.message) - } - if (error) { - throw error - } - return data -} diff --git a/starters/svelte/client/pages/index.svelte b/starters/svelte/client/pages/index.svelte index 3afe5f1..fae262d 100644 --- a/starters/svelte/client/pages/index.svelte +++ b/starters/svelte/client/pages/index.svelte @@ -11,7 +11,7 @@ import logo from '/assets/logo.svg' import { Link } from 'svelte-routing' </script> -<img src={logo} /> +<img src={logo} alt="Fastify DX" /> <h1>Welcome to Fastify DX for Svelte!</h1> <ul class="columns-2"> <li><Link to="/using-data">/using-data</Link> demonstrates how to diff --git a/starters/svelte/client/root.svelte b/starters/svelte/client/root.svelte index 64a1f6e..67d6ee9 100644 --- a/starters/svelte/client/root.svelte +++ b/starters/svelte/client/root.svelte @@ -5,24 +5,19 @@ import { Router, Route } from 'svelte-routing' import DXRoute from '/dx:route.svelte' export let url = null -export let head -export let routes -export let routeMap -export let ctxHydration +export let payload -const isServer = typeof process !== 'undefined' -const state = proxy(ctxHydration.state) +let state = proxy(payload.serverRoute.state) </script> <Router url="{url}"> - {#each routes as { path, component }} + {#each payload.routes as { path, component }} <Route path="{path}" let:location> <DXRoute + path={path} location={location} - head={head} - state={state} - ctx={routeMap[path]} - ctxHydration={ctxHydration} + state={state} + payload={payload} component={component} /> </Route> {/each} diff --git a/starters/svelte/package.json b/starters/svelte/package.json index fcd6d34..3f5f031 100644 --- a/starters/svelte/package.json +++ b/starters/svelte/package.json @@ -22,7 +22,6 @@ "@babel/eslint-parser": "^7.16.0", "@babel/preset-react": "^7.16.0", "@sveltejs/vite-plugin-svelte": "^1.0.0-next.49", - "@vitejs/plugin-react": "^1.3.2", "eslint": "^7.32.0", "eslint-config-standard": "^16.0.2", "eslint-plugin-import": "^2.22.1", diff --git a/starters/vue/package.json b/starters/vue/package.json index 890352b..369391d 100644 --- a/starters/vue/package.json +++ b/starters/vue/package.json @@ -4,7 +4,7 @@ "dev": "node server.js --dev", "build": "npm run build:client && npm run build:server", "serve": "node server.js", - "devinstall": "zx ../../devinstall.mjs react -- node server.js --dev", + "devinstall": "zx ../../devinstall.mjs vue -- node server.js --dev", "build:client": "vite build --outDir dist/client --ssrManifest", "build:server": "vite build --outDir dist/server --ssr /index.js", "lint": "eslint . --ext .js,.jsx --fix" From d85e43e73bebcfe37b3e7e55f094acf393ca08f9 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:15:30 -0300 Subject: [PATCH 04/20] wip svelte --- docs/svelte/basic-setup.md | 28 +++++++----- docs/svelte/data-prefetching.md | 4 +- docs/svelte/meta-tags.md | 3 +- docs/svelte/project-structure.md | 5 +-- docs/svelte/rendering-modes.md | 34 ++------------- docs/svelte/route-context.md | 6 +-- docs/svelte/route-enter.md | 2 +- docs/svelte/route-layouts.md | 53 ++++++++--------------- docs/svelte/routing-config.md | 2 +- docs/svelte/virtual-modules.md | 17 ++++---- starters/svelte/client/pages/index.svelte | 2 - starters/svelte/vite.config.js | 3 +- 12 files changed, 60 insertions(+), 99 deletions(-) diff --git a/docs/svelte/basic-setup.md b/docs/svelte/basic-setup.md index faab980..8a533dc 100644 --- a/docs/svelte/basic-setup.md +++ b/docs/svelte/basic-setup.md @@ -1,10 +1,10 @@ -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<su<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> ## 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. +The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte) 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. @@ -19,33 +19,41 @@ The starter template's `server.js` file: ```js import Fastify from 'fastify' import FastifyVite from 'fastify-vite' -import FastifyDXVue from 'fastify-dx-vue' +import FastifyDXSvelte from 'fastify-dx-svelte' const server = Fastify() await server.register(FastifyVite, { root: import.meta.url, - renderer: FastifyDXVue, + renderer: FastifyDXSvelte, }) 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: +The starter template's [`vite.config.js`](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/vite.config.js) file: ```js -import viteVue from '@vitejs/plugin-vue' -import viteVueFastifyDX from 'fastify-dx-vue/plugin' +import { join, dirname } from 'path' +import { fileURLToPath } from 'url' + +import { svelte as viteSvelte } from '@sveltejs/vite-plugin-svelte' +import viteSvelteFastifyDX from 'fastify-dx-svelte/plugin' import unocss from 'unocss/vite' +import { extractorSvelte } from '@unocss/core' const path = fileURLToPath(import.meta.url) const root = join(dirname(path), 'client') const plugins = [ - viteVue(), - viteVueFastifyDX(), - unocss() + unocss({ extractors: [extractorSvelte] }), + viteSvelte({ + compilerOptions: { + hydratable: true, + } + }), + viteSvelteFastifyDX(), ] export default { root, plugins } diff --git a/docs/svelte/data-prefetching.md b/docs/svelte/data-prefetching.md index 6eae389..94eb38f 100644 --- a/docs/svelte/data-prefetching.md +++ b/docs/svelte/data-prefetching.md @@ -1,10 +1,10 @@ -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> ## 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. +Fastify DX for Svelte implements the `getData()` hook from the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md) to solve this problem. ### `getData(ctx)` diff --git a/docs/svelte/meta-tags.md b/docs/svelte/meta-tags.md index aae4487..a8cbbdf 100644 --- a/docs/svelte/meta-tags.md +++ b/docs/svelte/meta-tags.md @@ -1,5 +1,4 @@ - -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> diff --git a/docs/svelte/project-structure.md b/docs/svelte/project-structure.md index b55f7a9..b33a213 100644 --- a/docs/svelte/project-structure.md +++ b/docs/svelte/project-structure.md @@ -1,11 +1,10 @@ - -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> ## Project Structure -The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/vue) looks like this: +The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte) looks like this: ``` ├── server.js diff --git a/docs/svelte/rendering-modes.md b/docs/svelte/rendering-modes.md index 95bc1b6..8aad700 100644 --- a/docs/svelte/rendering-modes.md +++ b/docs/svelte/rendering-modes.md @@ -1,36 +1,10 @@ - -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> # 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 -<template> - <Message :secs="2" /> - <Message :secs="4" /> - <Message :secs="6" /> -</template> - -<script> -import Message from '/components/Message.vue' - -export const streaming = true - -export default { - components: { Message }, -} -</script> -``` - -[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). - +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). Fastify DX for Svelte supports all of these modes minus streaming, which is currently not yet supported by Svelte itself. ## `serverOnly` @@ -48,7 +22,7 @@ export const serverOnly = true </script> ``` -[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). +[This example](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/client/pages/server-only.svelte) is part of the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte). ## `clientOnly` @@ -66,4 +40,4 @@ export const clientOnly = true </script> ``` -[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). +[This example](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/client/pages/client-only.svelte) is part of the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte). diff --git a/docs/svelte/route-context.md b/docs/svelte/route-context.md index a34db77..d8591ba 100644 --- a/docs/svelte/route-context.md +++ b/docs/svelte/route-context.md @@ -1,4 +1,4 @@ -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> @@ -25,7 +25,7 @@ const { 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. +See the [full example](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/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. @@ -57,7 +57,7 @@ export async function addTodoItem (state, item) { } ``` -See the [full example](https://github.com/fastify/fastify-dx/blob/main/starters/vue/client/context.js) in the starter template. +See the [full example](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/client/context.js) in the starter template. ### The `useRouteContext()` hook diff --git a/docs/svelte/route-enter.md b/docs/svelte/route-enter.md index ec20f27..64e66a3 100644 --- a/docs/svelte/route-enter.md +++ b/docs/svelte/route-enter.md @@ -1,4 +1,4 @@ -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> diff --git a/docs/svelte/route-layouts.md b/docs/svelte/route-layouts.md index a5fbf0c..1e258ec 100644 --- a/docs/svelte/route-layouts.md +++ b/docs/svelte/route-layouts.md @@ -1,56 +1,39 @@ -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> ## 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. +Fastify DX will automatically load layouts from the `layouts/` folder. By default, `/dx:layouts/default.svelte` is used — that is, if a project is missing a `layouts/defaults.svelte` 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. +See the section on [Virtual Modules](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/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: +See [`pages/using-auth.svelte`](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/pages/using-auth.svelte) 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 -<template> - <div class="contents"> - <template v-if="!state.user.authenticated"> - <p>This route needs authentication.</p> - <button @click="authenticate"> - Click this button to authenticate. - </button> - </template> - <slot v-else></slot> - </div> -</template> +That'll will cause the route to be wrapped in the layout component exported by [`layouts/auth.svelte`](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/layouts/auth.svelte): +```svelte <script> import { useRouteContext } from '/dx:core.js' - -export default { - setup () { - const { actions, state } = useRouteContext() - return { - state, - authenticate: () => actions.authenticate(state) - } - } -} +const { snapshot, actions, state } = useRouteContext() </script> + +<div class="contents"> + {#if !$snapshot.user.authenticated} + <p>This route needs authentication.</p> + <button on:click={() => actions.authenticate(state)}> + Click this button to authenticate. + </button> + {:else} + <slot /> + {/if} +</div> ``` Note that like routes, it has access to `useRouteContext()`. - - - - - - - diff --git a/docs/svelte/routing-config.md b/docs/svelte/routing-config.md index 44d0f84..e560d4c 100644 --- a/docs/svelte/routing-config.md +++ b/docs/svelte/routing-config.md @@ -1,4 +1,4 @@ -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> diff --git a/docs/svelte/virtual-modules.md b/docs/svelte/virtual-modules.md index 2f79d4c..f7fc9ba 100644 --- a/docs/svelte/virtual-modules.md +++ b/docs/svelte/virtual-modules.md @@ -1,5 +1,4 @@ - -<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> @@ -7,13 +6,13 @@ **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. +Fastify DX virtual modules are **fully ejectable**. For instance, the starter template relies on the `/dx:root.svelte` virtual module to provide the Svelte shell of your application. If you copy the `root.vue` file [from the fastify-dx-svelte package](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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: +This is the root Svelte 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' @@ -64,7 +63,7 @@ export default import.meta.env.SSR : 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. +See [the full file](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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: @@ -89,7 +88,7 @@ Implements `useRouteContext()` and `createBeforeEachHandler()`, used by `core.js > 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). +See its full definition [here](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/core.js). ### `/dx:create.js` @@ -142,7 +141,7 @@ export default async function create (ctx) { } ``` -What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/create.js). +What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/create.js). ### `/dx:layout.vue` @@ -180,7 +179,7 @@ export default { </script> ``` -What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/layout.vue). +What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/layout.vue). ### `/dx:mount.js` @@ -188,4 +187,4 @@ This is the file `index.html` links to by default. It sets up the application wi <b>You'll rarely need to customize this file.</b> -[See the full file](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-vue/virtual/mount.js) for the `mount()` function definition. +[See the full file](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/mount.js) for the `mount()` function definition. diff --git a/starters/svelte/client/pages/index.svelte b/starters/svelte/client/pages/index.svelte index fae262d..9347ebd 100644 --- a/starters/svelte/client/pages/index.svelte +++ b/starters/svelte/client/pages/index.svelte @@ -28,6 +28,4 @@ import { Link } from 'svelte-routing' up a route for rendering on the client only (disables SSR).</li> <li><Link to="/server-only">/server-only</Link> demonstrates how to set up a route for rendering on the server only (sends no JavaScript).</li> - <li><Link to="/streaming">/streaming</Link> demonstrates how to set - up a route for SSR in streaming mode.</li> </ul> diff --git a/starters/svelte/vite.config.js b/starters/svelte/vite.config.js index 412b720..6091229 100644 --- a/starters/svelte/vite.config.js +++ b/starters/svelte/vite.config.js @@ -4,18 +4,19 @@ import { fileURLToPath } from 'url' import { svelte as viteSvelte } from '@sveltejs/vite-plugin-svelte' import viteSvelteFastifyDX from 'fastify-dx-svelte/plugin' import unocss from 'unocss/vite' +import { extractorSvelte } from '@unocss/core' const path = fileURLToPath(import.meta.url) const root = join(dirname(path), 'client') const plugins = [ + unocss({ extractors: [extractorSvelte] }), viteSvelte({ compilerOptions: { hydratable: true, } }), viteSvelteFastifyDX(), - unocss() ] export default { From 490aa68d949ac9e214aac198e7c1d41233498cce Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:23:14 -0300 Subject: [PATCH 05/20] wip svelte --- docs/svelte/data-prefetching.md | 2 +- docs/svelte/project-structure.md | 30 +++++++++--------- docs/svelte/route-enter.md | 2 +- docs/vue/virtual-modules.md | 54 +++++++++++++------------------- 4 files changed, 39 insertions(+), 49 deletions(-) diff --git a/docs/svelte/data-prefetching.md b/docs/svelte/data-prefetching.md index 94eb38f..6ccd15a 100644 --- a/docs/svelte/data-prefetching.md +++ b/docs/svelte/data-prefetching.md @@ -10,7 +10,7 @@ Fastify DX for Svelte implements the `getData()` hook from the [URMA specificati 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. +The objet returned by `getData()` gets automatically assigned as `data` in the [universal route context](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md) object and is accessible from `getMeta()` and `onEnter()` hooks and also via the `useRouteContext()` hook. ```vue <template> diff --git a/docs/svelte/project-structure.md b/docs/svelte/project-structure.md index b33a213..31ed31b 100644 --- a/docs/svelte/project-structure.md +++ b/docs/svelte/project-structure.md @@ -11,27 +11,27 @@ The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/s ├── client/ | ├── index.js | ├── context.js -| ├── root.vue +| ├── root.svelte | ├── index.html | ├── layouts/ -| | ├── default.vue -| | └── auth.vue +| | ├── default.svelte +| | └── auth.svelte | └── pages/ -| ├── index.vue -| ├── client-only.vue -| ├── server-only.vue -| ├── streaming.vue -| ├── using-data.vue -| └── using-store.vue +| ├── index.svelte +| ├── client-only.svelte +| ├── server-only.svelte +| ├── streaming.svelte +| ├── using-data.svelte +| └── using-store.svelte ├── 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. +Several internal files are provided as virtual modules by Fastify DX. They are located inside the `fastify-dx-svelte` 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 +[virtual-modules]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/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 Vue** as a renderer adapter to **fastify-vite**. @@ -39,7 +39,7 @@ The `client/index.js` file is your Vite server entry point, it's the file that p > 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). +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/svelte/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. @@ -51,12 +51,12 @@ 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 +[routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/routing-config.md 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 +[routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/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 +[route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md diff --git a/docs/svelte/route-enter.md b/docs/svelte/route-enter.md index 64e66a3..c5c2985 100644 --- a/docs/svelte/route-enter.md +++ b/docs/svelte/route-enter.md @@ -10,7 +10,7 @@ If a route module exports a `onEnter()` function, it's executed before the route 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 +[route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md ```html <template> diff --git a/docs/vue/virtual-modules.md b/docs/vue/virtual-modules.md index 2f79d4c..c939d58 100644 --- a/docs/vue/virtual-modules.md +++ b/docs/vue/virtual-modules.md @@ -11,45 +11,35 @@ Fastify DX virtual modules are **fully ejectable**. For instance, the starter te 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` +### `/dx:root.svelte` -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: +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. 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. -```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 +```svelte <script> import 'uno.css' -</script> +import { proxy } from 'sveltio' +import { Router, Route } from 'svelte-routing' +import DXRoute from '/dx:route.svelte' -<script setup> -import Layout from '/dx:layout.vue' -</script> +export let url = null +export let payload -<template> - <router-view v-slot="{ Component }"> - <Suspense> - <Layout> - <component - :is="Component" - :key="$route.path" - /> - </Layout> - </Suspense> - </router-view> -</template> +let state = proxy(payload.serverRoute.state) +</script> +<Router url="{url}"> + {#each payload.routes as { path, component }} + <Route path="{path}" let:location> + <DXRoute + path={path} + location={location} + state={state} + payload={payload} + component={component} /> + </Route> + {/each} +</Router> ``` Note that a top-level `<Suspense>` 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. From 54628c731ddd7a224b8ac65c8e944dd485f1228b Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:31:10 -0300 Subject: [PATCH 06/20] wip svelte --- docs/svelte/index.md | 2 +- docs/svelte/meta-tags.md | 4 +- docs/svelte/routing-config.md | 2 +- docs/svelte/virtual-modules.md | 91 ++++++++++------------------------ 4 files changed, 31 insertions(+), 68 deletions(-) diff --git a/docs/svelte/index.md b/docs/svelte/index.md index ac3ddd0..f6d9f1b 100644 --- a/docs/svelte/index.md +++ b/docs/svelte/index.md @@ -1,4 +1,4 @@ -# 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/) +# fastify-dx-svelte [![NPM version](https://img.shields.io/npm/v/fastify-dx-svelte.svg?style=flat)](https://www.npmjs.com/package/fastify-dx-svelte) [![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-svelte/README.md#introduction) - [**Quick Start**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md#quick-start) diff --git a/docs/svelte/meta-tags.md b/docs/svelte/meta-tags.md index a8cbbdf..8019bb2 100644 --- a/docs/svelte/meta-tags.md +++ b/docs/svelte/meta-tags.md @@ -6,13 +6,13 @@ Following the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md), Fastify DX renders `<head>` elements independently from the SSR phase. This allows you to fetch data for populating the first `<meta>` tags and stream them right away to the client, and only then perform SSR. -> Additional `<link>` 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). +> Additional `<link>` 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-svelte/index.js#L57) to [fastify-vite](https://github.com/fastify/fastify-vite). ### `getMeta()` To populate `<title>`, `<meta>` and `<link>` 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. +It receives the [route context](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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 <template> diff --git a/docs/svelte/routing-config.md b/docs/svelte/routing-config.md index e560d4c..eacc646 100644 --- a/docs/svelte/routing-config.md +++ b/docs/svelte/routing-config.md @@ -36,7 +36,7 @@ const plugins = [ 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). +Additionally, [**you can provide your own routes**](https://github.com/fastify/fastify-dx/tree/dev/packages/fastify-dx-svelte#dxroutesjs). ```jsx <template> diff --git a/docs/svelte/virtual-modules.md b/docs/svelte/virtual-modules.md index f7fc9ba..7732c4b 100644 --- a/docs/svelte/virtual-modules.md +++ b/docs/svelte/virtual-modules.md @@ -1,3 +1,4 @@ + <sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> @@ -6,53 +7,41 @@ **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.svelte` virtual module to provide the Svelte shell of your application. If you copy the `root.vue` file [from the fastify-dx-svelte package](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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. +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-svelte package](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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 Svelte 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. +### `/dx:root.svelte` -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. +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. 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 +```svelte <script> import 'uno.css' -</script> +import { proxy } from 'sveltio' +import { Router, Route } from 'svelte-routing' +import DXRoute from '/dx:route.svelte' -<script setup> -import Layout from '/dx:layout.vue' -</script> +export let url = null +export let payload -<template> - <router-view v-slot="{ Component }"> - <Suspense> - <Layout> - <component - :is="Component" - :key="$route.path" - /> - </Layout> - </Suspense> - </router-view> -</template> +let state = proxy(payload.serverRoute.state) +</script> +<Router url="{url}"> + {#each payload.routes as { path, component }} + <Route path="{path}" let:location> + <DXRoute + path={path} + location={location} + state={state} + payload={payload} + component={component} /> + </Route> + {/each} +</Router> ``` -Note that a top-level `<Suspense>` 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. @@ -143,40 +132,14 @@ export default async function create (ctx) { What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/create.js). -### `/dx:layout.vue` +### `/dx:layouts.js` -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. +This is responsible for loading **layout components**. It's part of `route.svelte` 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. <b>You'll rarely need to customize this file.</b> -```vue -<template> - <component :is="layout"> - <slot /> - </component> -</template> +```svelte -<script> -import { defineAsyncComponent, inject } from 'vue' -import { routeLayout } from '/dx:core.js' - -const DefaultLayout = () => import('/dx:layouts/default.vue') -const appLayouts = import.meta.glob('/layouts/*.vue') - -appLayouts['/layouts/default.vue'] ??= DefaultLayout - -export default { - setup: () => ({ - layout: inject(routeLayout) - }), - components: Object.fromEntries( - Object.keys(appLayouts).map((path) => { - const name = path.slice(9, -4) - return [name, defineAsyncComponent(appLayouts[path])] - }) - ) -} -</script> ``` What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/layout.vue). From 7d9501953839f915685462f71b65106985fa3bb4 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:32:28 -0300 Subject: [PATCH 07/20] wip svelte --- docs/svelte/route-context.md | 10 ++++------ docs/svelte/routing-config.md | 14 ++++++-------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/svelte/route-context.md b/docs/svelte/route-context.md index d8591ba..4473c75 100644 --- a/docs/svelte/route-context.md +++ b/docs/svelte/route-context.md @@ -67,15 +67,13 @@ By default, It includes reference to `data` — which is automatically populated It automatically causes the component to be suspended if the `getData()`, `getMeta()` and `onEnter()` functions are asynchronous. -```vue -<template> - <p>{data.message}</p> -</template> - -<script setup> +```svelte +<script> import { useRouteContext } from '/dx:core.js' const { data } = useRouteContext() </script> + +<p>{data.message}</p> ``` ### Execution order diff --git a/docs/svelte/routing-config.md b/docs/svelte/routing-config.md index eacc646..77cb2ac 100644 --- a/docs/svelte/routing-config.md +++ b/docs/svelte/routing-config.md @@ -14,7 +14,7 @@ Dynamic route parameters follow the [Next.js convention](https://nextjs.org/docs // ... const plugins = [ // ... - viteVueFastifyDX({ paramPattern: /\$(\w+)/ }), + viteSvelteFastifyDX({ paramPattern: /\$(\w+)/ }), ] ``` @@ -28,7 +28,7 @@ Since this setting is passed to [Vite's glob importers](https://vitejs.dev/guide // ... const plugins = [ // ... - viteVueFastifyDX({ globPattern: '/views/**/*.vue' }), + viteSvelteFastifyDX({ globPattern: '/views/**/*.svelte' }), ] ``` @@ -38,12 +38,10 @@ You also can export a `path` constant from your route modules, in which case its Additionally, [**you can provide your own routes**](https://github.com/fastify/fastify-dx/tree/dev/packages/fastify-dx-svelte#dxroutesjs). -```jsx -<template> - <p>Route with path export</p> -</template> - -<script> +```svelte +<script context="module"> export const path = '/my-page' </script> + +<p>Route with path export</p> ``` From 7aa69b66f475e634b059a29af651349aafec7973 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:38:27 -0300 Subject: [PATCH 08/20] wip svelte --- docs/svelte/rendering-modes.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/svelte/rendering-modes.md b/docs/svelte/rendering-modes.md index 8aad700..74398d3 100644 --- a/docs/svelte/rendering-modes.md +++ b/docs/svelte/rendering-modes.md @@ -12,14 +12,12 @@ If a route module exports `serverOnly` set to `true`, only SSR will take place. 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 -<template> - <p>This route is rendered on the server only!</p> -</template> - -<script> +```svelte +<script context="module"> export const serverOnly = true </script> + +<p>This route is rendered on the server only!</p> ``` [This example](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/client/pages/server-only.svelte) is part of the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte). @@ -30,14 +28,12 @@ If a route module exports `clientOnly` set to `true`, no SSR will take place, on 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 -<template> - <p>This route is rendered on the client only!</p> -</template> - -<script> +```svelte +<script context="module"> export const clientOnly = true </script> + +<p>This route is rendered on the client only!</p> ``` [This example](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/client/pages/client-only.svelte) is part of the [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte). From abe4769ed57191f037e6fc37d8e7a152f8346faf Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:44:27 -0300 Subject: [PATCH 09/20] wip svelte --- docs/svelte/basic-setup.md | 23 ++++++++++++++++++++++- docs/svelte/data-prefetching.md | 24 +++++++++--------------- docs/svelte/route-enter.md | 10 ++++------ 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/docs/svelte/basic-setup.md b/docs/svelte/basic-setup.md index 8a533dc..a5c16f5 100644 --- a/docs/svelte/basic-setup.md +++ b/docs/svelte/basic-setup.md @@ -1,4 +1,4 @@ -<su<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> +<sub>**Go back to the [index](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md).**</sub> <br> @@ -64,3 +64,24 @@ Note that you only need to use Fastify DX's Vite plugin, which includes all func </td> </tr> </table> + +### Route exports + +Fastify DX picks up exports from route modules to determine route behavior and functionality, as per the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md). + +To add those exports, you must use `<script context="module">` (Svelte-specific syntax) which determines the script that runs in the general module namespace for a Svelte component. So in Fastify DX Svelte applications, it's commonplace to have two code blocks, a regular one and another with `context` set to `module`: + +```svelte +<script context="module"> +export function getData () { + return { message: 'Hello from getData!' } +} +<script> + +<script> +import { useRouteContext } = '/dx:core.js' +const { data } = useRouteContext() +</script> + +<p>{data.message}</p> +``` diff --git a/docs/svelte/data-prefetching.md b/docs/svelte/data-prefetching.md index 6ccd15a..533f19b 100644 --- a/docs/svelte/data-prefetching.md +++ b/docs/svelte/data-prefetching.md @@ -12,25 +12,19 @@ This hook is set up in a way that it runs server-side before any SSR takes place The objet returned by `getData()` gets automatically assigned as `data` in the [universal route context](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md) object and is accessible from `getMeta()` and `onEnter()` hooks and also via the `useRouteContext()` hook. -```vue -<template> - <p>{data.message}</p> -</template> - -<script> -import { useRouteContext } from '/dx:core.js' - -export function getData (ctx) { +```svelte +<script context="module"> +export let getData = (ctx) => { return { message: 'Hello from getData!', } } +</script> -export default { - setup () { - const { data } = useRouteContext() - return { data } - } -} +<script> +import { useRouteContext } from '/dx:core.js' +const { data } = useRouteContext() </script> + +<p>{data.message}</p> ``` diff --git a/docs/svelte/route-enter.md b/docs/svelte/route-enter.md index c5c2985..07f1207 100644 --- a/docs/svelte/route-enter.md +++ b/docs/svelte/route-enter.md @@ -12,18 +12,16 @@ It receives the [universal route context][route-context] as first parameter, so [route-context]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md -```html -<template> - <p>No pre-rendered HTML sent to the browser.</p> -</template> - -<script> +```svelte +<script context="module"> export function onEnter (ctx) { if (ctx.server?.underPressure) { ctx.clientOnly = true } } </script> + +<p>No pre-rendered HTML sent to the browser.</p> ``` 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 ed9d1a366fb47ac143425f340745fe5f04b0b4c6 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 21:49:50 -0300 Subject: [PATCH 10/20] wip svelte --- docs/react/basic-setup.md | 2 +- docs/svelte/basic-setup.md | 4 ++-- docs/svelte/data-prefetching.md | 2 +- docs/svelte/rendering-modes.md | 4 ++-- docs/svelte/route-context.md | 2 +- docs/svelte/route-enter.md | 2 +- docs/svelte/route-layouts.md | 2 +- docs/svelte/routing-config.md | 2 +- docs/svelte/virtual-modules.md | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/react/basic-setup.md b/docs/react/basic-setup.md index cf627ca..36c4f57 100644 --- a/docs/react/basic-setup.md +++ b/docs/react/basic-setup.md @@ -6,7 +6,7 @@ The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/react) 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. +If you want a 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/): diff --git a/docs/svelte/basic-setup.md b/docs/svelte/basic-setup.md index a5c16f5..9137bab 100644 --- a/docs/svelte/basic-setup.md +++ b/docs/svelte/basic-setup.md @@ -4,7 +4,7 @@ ## Basic setup -The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte) 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. +The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/svelte) 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. @@ -71,7 +71,7 @@ Fastify DX picks up exports from route modules to determine route behavior and f To add those exports, you must use `<script context="module">` (Svelte-specific syntax) which determines the script that runs in the general module namespace for a Svelte component. So in Fastify DX Svelte applications, it's commonplace to have two code blocks, a regular one and another with `context` set to `module`: -```svelte +```html <script context="module"> export function getData () { return { message: 'Hello from getData!' } diff --git a/docs/svelte/data-prefetching.md b/docs/svelte/data-prefetching.md index 533f19b..a692298 100644 --- a/docs/svelte/data-prefetching.md +++ b/docs/svelte/data-prefetching.md @@ -12,7 +12,7 @@ This hook is set up in a way that it runs server-side before any SSR takes place The objet returned by `getData()` gets automatically assigned as `data` in the [universal route context](https://github.com/fastify/fastify-dx/blob/main/docs/svelte/route-context.md) object and is accessible from `getMeta()` and `onEnter()` hooks and also via the `useRouteContext()` hook. -```svelte +```html <script context="module"> export let getData = (ctx) => { return { diff --git a/docs/svelte/rendering-modes.md b/docs/svelte/rendering-modes.md index 74398d3..8f2a237 100644 --- a/docs/svelte/rendering-modes.md +++ b/docs/svelte/rendering-modes.md @@ -12,7 +12,7 @@ If a route module exports `serverOnly` set to `true`, only SSR will take place. 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. -```svelte +```html <script context="module"> export const serverOnly = true </script> @@ -28,7 +28,7 @@ If a route module exports `clientOnly` set to `true`, no SSR will take place, on 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. -```svelte +```html <script context="module"> export const clientOnly = true </script> diff --git a/docs/svelte/route-context.md b/docs/svelte/route-context.md index 4473c75..72dc414 100644 --- a/docs/svelte/route-context.md +++ b/docs/svelte/route-context.md @@ -67,7 +67,7 @@ By default, It includes reference to `data` — which is automatically populated It automatically causes the component to be suspended if the `getData()`, `getMeta()` and `onEnter()` functions are asynchronous. -```svelte +```html <script> import { useRouteContext } from '/dx:core.js' const { data } = useRouteContext() diff --git a/docs/svelte/route-enter.md b/docs/svelte/route-enter.md index 07f1207..84abad8 100644 --- a/docs/svelte/route-enter.md +++ b/docs/svelte/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/svelte/route-context.md -```svelte +```html <script context="module"> export function onEnter (ctx) { if (ctx.server?.underPressure) { diff --git a/docs/svelte/route-layouts.md b/docs/svelte/route-layouts.md index 1e258ec..7f386d5 100644 --- a/docs/svelte/route-layouts.md +++ b/docs/svelte/route-layouts.md @@ -18,7 +18,7 @@ export const layout = 'auth' That'll will cause the route to be wrapped in the layout component exported by [`layouts/auth.svelte`](https://github.com/fastify/fastify-dx/blob/main/starters/svelte/layouts/auth.svelte): -```svelte +```html <script> import { useRouteContext } from '/dx:core.js' const { snapshot, actions, state } = useRouteContext() diff --git a/docs/svelte/routing-config.md b/docs/svelte/routing-config.md index 77cb2ac..6a170ff 100644 --- a/docs/svelte/routing-config.md +++ b/docs/svelte/routing-config.md @@ -38,7 +38,7 @@ You also can export a `path` constant from your route modules, in which case its Additionally, [**you can provide your own routes**](https://github.com/fastify/fastify-dx/tree/dev/packages/fastify-dx-svelte#dxroutesjs). -```svelte +```html <script context="module"> export const path = '/my-page' </script> diff --git a/docs/svelte/virtual-modules.md b/docs/svelte/virtual-modules.md index 7732c4b..fc0ba4d 100644 --- a/docs/svelte/virtual-modules.md +++ b/docs/svelte/virtual-modules.md @@ -15,7 +15,7 @@ Aside from `root.vue`, the starter template comes with two other virtual modules 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. 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. -```svelte +```html <script> import 'uno.css' import { proxy } from 'sveltio' @@ -138,7 +138,7 @@ This is responsible for loading **layout components**. It's part of `route.svelt <b>You'll rarely need to customize this file.</b> -```svelte +```html ``` From 30b4446add44115989fc07b0883b27ddeaea8c07 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:08:48 -0300 Subject: [PATCH 11/20] wip svelte --- docs/svelte/data-prefetching.md | 2 +- docs/svelte/meta-tags.md | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/svelte/data-prefetching.md b/docs/svelte/data-prefetching.md index a692298..d010712 100644 --- a/docs/svelte/data-prefetching.md +++ b/docs/svelte/data-prefetching.md @@ -14,7 +14,7 @@ The objet returned by `getData()` gets automatically assigned as `data` in the [ ```html <script context="module"> -export let getData = (ctx) => { +export function getData (ctx) { return { message: 'Hello from getData!', } diff --git a/docs/svelte/meta-tags.md b/docs/svelte/meta-tags.md index 8019bb2..d406447 100644 --- a/docs/svelte/meta-tags.md +++ b/docs/svelte/meta-tags.md @@ -14,12 +14,8 @@ To populate `<title>`, `<meta>` and `<link>` elements, export a `getMeta()` func It receives the [route context](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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 -<template> - <p>Route with meta tags.</p> -</template> - -<script> +```html +<script context="module"> export function getMeta (ctx) { return { title: 'Route Title', @@ -29,4 +25,6 @@ export function getMeta (ctx) { } } </script> + +<p>Route with meta tags.</p> ``` From 6f4f9c351ae2d5667a7ed3c7cf005cf515798656 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:12:49 -0300 Subject: [PATCH 12/20] wip svelte --- docs/svelte/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/svelte/index.md b/docs/svelte/index.md index f6d9f1b..b54efcf 100644 --- a/docs/svelte/index.md +++ b/docs/svelte/index.md @@ -17,7 +17,7 @@ **Fastify DX for Svelte** is a renderer adapter for [**fastify-vite**](https://github.com/fastify/fastify-vite). -It lets you run and SSR (server-side render) **Vue 3 applications built with Vite** on [Fastify](https://fastify.io/), with a minimal and transparent **server-first approach** — everything starts with `server.js`, your actual Fastify server). +It lets you run and SSR (server-side render) **Svelte applications built with Vite** on [Fastify](https://fastify.io/), with a minimal and transparent **server-first approach** — everything starts with `server.js`, your actual Fastify server). It also provides a set of built-in utilities for ease of development and managing a universal JavaScript context (SSR to CSR), very much like **Nuxt.js**, **Next.js** and **Remix**. All **Fastify DX** framework adapters implement the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md) and have almost the same API, with only minimal differences due to specific framework APIs or idioms. @@ -25,7 +25,7 @@ It is a **fast**, **lightweight** alternative to Nuxt.js packed with **Developer 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/), [Valtio](https://github.com/pmndrs/valtio) and [Svelte Routing](https://github.com/EmilTholin/svelte-routing). -[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/vue-v0.0.1). +[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/svelte-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. @@ -72,7 +72,7 @@ It also includes some _**opinionated**_ essentials: - [**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. Svelte support is provided via [Sveltio](https://github.com/wobsoriano/sveltio) by [Robert Soriano](https://robsoriano.com/). +- [**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. <br>Svelte support is provided via [Sveltio](https://github.com/wobsoriano/sveltio) by [Robert Soriano](https://robsoriano.com/). ## Package Scripts From 7f4bd9a720622ae55c95ab01623492a4bc210e9a Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:13:53 -0300 Subject: [PATCH 13/20] fix --- docs/react/project-structure.md | 28 ++++++++++++++-------------- docs/svelte/project-structure.md | 28 ++++++++++++++-------------- docs/vue/project-structure.md | 28 ++++++++++++++-------------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/docs/react/project-structure.md b/docs/react/project-structure.md index 9a1dd50..e2f4a1d 100644 --- a/docs/react/project-structure.md +++ b/docs/react/project-structure.md @@ -10,20 +10,20 @@ The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/r ``` ├── server.js ├── client/ -| ├── index.js -| ├── context.js -| ├── root.jsx -| ├── index.html -| ├── layouts/ -| | ├── default.jsx -| | └── auth.jsx -| └── pages/ -| ├── index.jsx -| ├── client-only.jsx -| ├── server-only.jsx -| ├── streaming.jsx -| ├── using-data.jsx -| └── using-store.jsx +│ ├── index.js +│ ├── context.js +│ ├── root.jsx +│ ├── index.html +│ ├── layouts/ +│ │ ├── default.jsx +│ │ └── auth.jsx +│ └── pages/ +│ ├── index.jsx +│ ├── client-only.jsx +│ ├── server-only.jsx +│ ├── streaming.jsx +│ ├── using-data.jsx +│ └── using-store.jsx ├── vite.config.js └── package.json ``` diff --git a/docs/svelte/project-structure.md b/docs/svelte/project-structure.md index 31ed31b..307d368 100644 --- a/docs/svelte/project-structure.md +++ b/docs/svelte/project-structure.md @@ -9,20 +9,20 @@ The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/s ``` ├── server.js ├── client/ -| ├── index.js -| ├── context.js -| ├── root.svelte -| ├── index.html -| ├── layouts/ -| | ├── default.svelte -| | └── auth.svelte -| └── pages/ -| ├── index.svelte -| ├── client-only.svelte -| ├── server-only.svelte -| ├── streaming.svelte -| ├── using-data.svelte -| └── using-store.svelte +│ ├── index.js +│ ├── context.js +│ ├── root.svelte +│ ├── index.html +│ ├── layouts/ +│ │ ├── default.svelte +│ │ └── auth.svelte +│ └── pages/ +│ ├── index.svelte +│ ├── client-only.svelte +│ ├── server-only.svelte +│ ├── streaming.svelte +│ ├── using-data.svelte +│ └── using-store.svelte ├── vite.config.js └── package.json ``` diff --git a/docs/vue/project-structure.md b/docs/vue/project-structure.md index b55f7a9..589d45d 100644 --- a/docs/vue/project-structure.md +++ b/docs/vue/project-structure.md @@ -10,20 +10,20 @@ The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/v ``` ├── 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 +│ ├── 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 ``` From 10ee6f366e0024dde1416372a8e636313b0f5064 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:21:04 -0300 Subject: [PATCH 14/20] fix --- docs/svelte/project-structure.md | 4 +- docs/svelte/virtual-modules.md | 167 +++++++++++++++++++------------ docs/vue/virtual-modules.md | 54 ++++++---- 3 files changed, 138 insertions(+), 87 deletions(-) diff --git a/docs/svelte/project-structure.md b/docs/svelte/project-structure.md index 307d368..8226412 100644 --- a/docs/svelte/project-structure.md +++ b/docs/svelte/project-structure.md @@ -33,7 +33,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/svelte/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 Vue** 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 Svelte** 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. @@ -53,7 +53,7 @@ The `client/pages/` directory contains your route modules, whose paths are dynam [routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/routing-config.md -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. +The `client/layouts/` directory contains your route layout modules, which can be associated to any route. By default, `layouts/default.svelte` 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.svelte`, to demonstrate a more advanced use of layouts. [routing-config]: https://github.com/fastify/fastify-dx/blob/main/docs/svelte/routing-config.md diff --git a/docs/svelte/virtual-modules.md b/docs/svelte/virtual-modules.md index fc0ba4d..dfae691 100644 --- a/docs/svelte/virtual-modules.md +++ b/docs/svelte/virtual-modules.md @@ -7,13 +7,13 @@ **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-svelte package](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/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. +Fastify DX virtual modules are **fully ejectable**. For instance, the starter template relies on the `/dx:root.svelte` virtual module to provide the Vue shell of your application. If you copy the `root.svelte` file [from the fastify-dx-svelte package](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/root.svelte) 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.svelte` 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. +Aside from `root.svelte`, the starter template comes with two other virtual modules already ejected and part of the local project — `context.js` and `layouts/default.svelte`. If you don't need to customize them, you can safely removed them from your project. ### `/dx:root.svelte` -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. 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. +This is the root Svelte component. It's provided as part of the starter template. You can use this file to add a common layout to all routes. 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. ```html <script> @@ -42,6 +42,101 @@ let state = proxy(payload.serverRoute.state) </Router> ``` +### `/dx:route.svelte` + +This is used by `root.svelte` to enhance your route modules with the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md). + +<b>You'll rarely need to customize this file.</b> + +```html +<script> +import { setContext } from 'svelte' +import Loadable from 'svelte-loadable' +import { routeContext, jsonDataFetch } from '/dx:core.js' +import layouts from '/dx:layouts.js' + +const isServer = import.meta.env.SSR + +setContext(routeContext, { + get routeContext () { + return ctx + } +}) + +export let path +export let component +export let payload +export let state +export let location + +let ctx = payload.routeMap[path] + +ctx.state = state +ctx.actions = payload.serverRoute.actions + +if (isServer) { + ctx.layout = payload.serverRoute.layout ?? 'default' + ctx.data = payload.serverRoute.data + ctx.state = state +} + +async function setup () { + if (payload.serverRoute.firstRender) { + ctx.data = payload.serverRoute.data + ctx.layout = payload.serverRoute.layout ?? 'default' + payload.serverRoute.firstRender = false + return + } + ctx.layout = ctx.layout ?? 'default' + const { getMeta, getData, onEnter } = await ctx.loader() + if (getData) { + try { + const fullPath = `${location.pathname}${location.search}` + const updatedData = await jsonDataFetch(fullPath) + if (!ctx.data) { + ctx.data = {} + } + if (updatedData) { + Object.assign(ctx.data, updatedData) + } + ctx.error = null + } catch (error) { + ctx.error = error + } + } + if (getMeta) { + const updatedMeta = await getMeta(ctx) + if (updatedMeta) { + payload.head.update(updatedMeta) + } + } + if (onEnter) { + const updatedData = await onEnter(ctx) + if (updatedData) { + Object.assign(ctx.data, updatedData) + } + } +} + +let setupClientRouteContext = !isServer && setup() +</script> + +{#if isServer} + <svelte:component this={layouts[ctx.layout].default}> + <svelte:component this={component} /> + </svelte:component> +{:else} +{#await setupClientRouteContext}{:then} + <svelte:component this={layouts[ctx.layout].default}> + <Loadable loader={component} /> + </svelte:component> +{/await} +{/if} +``` + +What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/route.svelte). + + ### `/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. @@ -60,7 +155,7 @@ If you want to use your own custom routes list, you must eject this file as-is a const routes = [ { path: '/', - component: () => import('/custom/index.vue'), + component: () => import('/custom/index.svelte'), } ] @@ -69,72 +164,18 @@ export default import.meta.env.SSR : hydrateRoutes(routes) ```` -### `/dx:core.js` +**Nested routes aren't supported yet.** -Implements `useRouteContext()` and `createBeforeEachHandler()`, used by `core.js`. -`DXApp` is imported by `root.vue` and encapsulates Fastify DX's route component API. +### `/dx:core.js` -> Vue Router's [nested routes](https://router.vuejs.org/guide/essentials/nested-routes.html) aren't supported yet. +Implements `useRouteContext()`. See its full definition [here](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/core.js). -### `/dx:create.js` - -This virtual module creates your root Vue component. - -This is where `root.vue` is imported. - -<b>You'll rarely need to customize this file.</b> - -```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-svelte/virtual/create.js). - ### `/dx:layouts.js` -This is responsible for loading **layout components**. It's part of `route.svelte` 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. +This is responsible for loading **layout components**. It's part of `route.svelte` by default. If a project has no `layouts/default.svelte` 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. <b>You'll rarely need to customize this file.</b> @@ -142,7 +183,7 @@ This is responsible for loading **layout components**. It's part of `route.svelt ``` -What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/layout.vue). +What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/layout.svelte). ### `/dx:mount.js` diff --git a/docs/vue/virtual-modules.md b/docs/vue/virtual-modules.md index c939d58..2f79d4c 100644 --- a/docs/vue/virtual-modules.md +++ b/docs/vue/virtual-modules.md @@ -11,35 +11,45 @@ Fastify DX virtual modules are **fully ejectable**. For instance, the starter te 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.svelte` +### `/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. 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. +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: -```svelte +```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 <script> import 'uno.css' -import { proxy } from 'sveltio' -import { Router, Route } from 'svelte-routing' -import DXRoute from '/dx:route.svelte' - -export let url = null -export let payload +</script> -let state = proxy(payload.serverRoute.state) +<script setup> +import Layout from '/dx:layout.vue' </script> -<Router url="{url}"> - {#each payload.routes as { path, component }} - <Route path="{path}" let:location> - <DXRoute - path={path} - location={location} - state={state} - payload={payload} - component={component} /> - </Route> - {/each} -</Router> +<template> + <router-view v-slot="{ Component }"> + <Suspense> + <Layout> + <component + :is="Component" + :key="$route.path" + /> + </Layout> + </Suspense> + </router-view> +</template> + ``` Note that a top-level `<Suspense>` 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. From bba4af919100660ff77ea868c31f126b606bb1cf Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:22:06 -0300 Subject: [PATCH 15/20] fix --- docs/svelte/virtual-modules.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/svelte/virtual-modules.md b/docs/svelte/virtual-modules.md index dfae691..1ba5dbb 100644 --- a/docs/svelte/virtual-modules.md +++ b/docs/svelte/virtual-modules.md @@ -179,11 +179,23 @@ This is responsible for loading **layout components**. It's part of `route.svelt <b>You'll rarely need to customize this file.</b> -```html +```js +import DefaultLayout from '/dx:layouts/default.svelte' + +const appLayouts = import.meta.globEager('/layouts/*.svelte') + +appLayouts['/layouts/default.svelte'] ??= DefaultLayout + +export default Object.fromEntries( + Object.keys(appLayouts).map((path) => { + const name = path.slice(9, -7) + return [name, appLayouts[path]] + }), +) ``` -What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/layout.svelte). +What you see above is its [full definition](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/virtual/layouts.js). ### `/dx:mount.js` From c2888d8055e32cf171915c2aa1eab3daf9c24b07 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:26:58 -0300 Subject: [PATCH 16/20] fix --- docs/react/route-context.md | 28 +++++++++++++++------------- docs/svelte/route-context.md | 28 +++++++++++++++------------- docs/vue/route-context.md | 28 +++++++++++++++------------- 3 files changed, 45 insertions(+), 39 deletions(-) diff --git a/docs/react/route-context.md b/docs/react/route-context.md index 27fb97c..fc67851 100644 --- a/docs/react/route-context.md +++ b/docs/react/route-context.md @@ -33,15 +33,10 @@ This example demonstrates how to use it to set up an universally available (SSR 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 + // Populate state.todoList on the server + ctx.state.todoList = ctx.server.db.todoList + // It'll get automatically serialized to the client on first render! } } @@ -49,11 +44,18 @@ 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) +// Must be a function so each request can have its own state +export const state = () => ({ + todoList: null, +}) + +export const actions = { + async addTodoItem (state, item) { + await $fetch.put('api/todo/items', { + json: { item }, + }) + state.todoList.push(item) + }, } ``` diff --git a/docs/svelte/route-context.md b/docs/svelte/route-context.md index 72dc414..cb152f1 100644 --- a/docs/svelte/route-context.md +++ b/docs/svelte/route-context.md @@ -33,15 +33,10 @@ This example demonstrates how to use it to set up an universally available (SSR 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 + // Populate state.todoList on the server + ctx.state.todoList = ctx.server.db.todoList + // It'll get automatically serialized to the client on first render! } } @@ -49,11 +44,18 @@ 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) +// Must be a function so each request can have its own state +export const state = () => ({ + todoList: null, +}) + +export const actions = { + async addTodoItem (state, item) { + await $fetch.put('api/todo/items', { + json: { item }, + }) + state.todoList.push(item) + }, } ``` diff --git a/docs/vue/route-context.md b/docs/vue/route-context.md index a34db77..ee63d1c 100644 --- a/docs/vue/route-context.md +++ b/docs/vue/route-context.md @@ -33,15 +33,10 @@ This example demonstrates how to use it to set up an universally available (SSR 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 + // Populate state.todoList on the server + ctx.state.todoList = ctx.server.db.todoList + // It'll get automatically serialized to the client on first render! } } @@ -49,11 +44,18 @@ 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) +// Must be a function so each request can have its own state +export const state = () => ({ + todoList: null, +}) + +export const actions = { + async addTodoItem (state, item) { + await $fetch.put('api/todo/items', { + json: { item }, + }) + state.todoList.push(item) + }, } ``` From eaa8f85938055fe047413a91390b2777bd09d381 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:31:21 -0300 Subject: [PATCH 17/20] fix --- docs/svelte/project-structure.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/svelte/project-structure.md b/docs/svelte/project-structure.md index 8226412..dc3132c 100644 --- a/docs/svelte/project-structure.md +++ b/docs/svelte/project-structure.md @@ -20,7 +20,6 @@ The [starter template](https://github.com/fastify/fastify-dx/tree/dev/starters/s │ ├── index.svelte │ ├── client-only.svelte │ ├── server-only.svelte -│ ├── streaming.svelte │ ├── using-data.svelte │ └── using-store.svelte ├── vite.config.js From 96e25bc09b017b81db2caf2062143ba7b147189d Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:41:57 -0300 Subject: [PATCH 18/20] fix --- packages/fastify-dx-svelte/.eslintrc | 25 ++----- packages/fastify-dx-svelte/README.md | 8 +-- packages/fastify-dx-svelte/index.js | 2 +- packages/fastify-dx-svelte/package.json | 19 +++--- packages/fastify-dx-svelte/plugin.cjs | 1 - .../fastify-dx-svelte/virtual/resource.js | 68 ------------------- .../fastify-dx-svelte/virtual/route.svelte | 2 +- 7 files changed, 23 insertions(+), 102 deletions(-) delete mode 100644 packages/fastify-dx-svelte/virtual/resource.js diff --git a/packages/fastify-dx-svelte/.eslintrc b/packages/fastify-dx-svelte/.eslintrc index 2ade438..e5461ce 100644 --- a/packages/fastify-dx-svelte/.eslintrc +++ b/packages/fastify-dx-svelte/.eslintrc @@ -4,29 +4,18 @@ requireConfigFile: false, ecmaVersion: 2021, sourceType: 'module', - babelOptions: { - presets: ['@babel/preset-react'], - }, - ecmaFeatures: { - jsx: true, - }, }, - extends: [ - 'plugin:react/recommended', - 'standard', - ], plugins: [ - 'react', + 'svelte3' + ], + overrides: [ + { + files: ['*.svelte'], + processor: 'svelte3/svelte3' + } ], 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-svelte/README.md b/packages/fastify-dx-svelte/README.md index ac3ddd0..b54efcf 100644 --- a/packages/fastify-dx-svelte/README.md +++ b/packages/fastify-dx-svelte/README.md @@ -1,4 +1,4 @@ -# 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/) +# fastify-dx-svelte [![NPM version](https://img.shields.io/npm/v/fastify-dx-svelte.svg?style=flat)](https://www.npmjs.com/package/fastify-dx-svelte) [![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-svelte/README.md#introduction) - [**Quick Start**](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md#quick-start) @@ -17,7 +17,7 @@ **Fastify DX for Svelte** is a renderer adapter for [**fastify-vite**](https://github.com/fastify/fastify-vite). -It lets you run and SSR (server-side render) **Vue 3 applications built with Vite** on [Fastify](https://fastify.io/), with a minimal and transparent **server-first approach** — everything starts with `server.js`, your actual Fastify server). +It lets you run and SSR (server-side render) **Svelte applications built with Vite** on [Fastify](https://fastify.io/), with a minimal and transparent **server-first approach** — everything starts with `server.js`, your actual Fastify server). It also provides a set of built-in utilities for ease of development and managing a universal JavaScript context (SSR to CSR), very much like **Nuxt.js**, **Next.js** and **Remix**. All **Fastify DX** framework adapters implement the [URMA specification](https://github.com/fastify/fastify-dx/blob/main/URMA.md) and have almost the same API, with only minimal differences due to specific framework APIs or idioms. @@ -25,7 +25,7 @@ It is a **fast**, **lightweight** alternative to Nuxt.js packed with **Developer 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/), [Valtio](https://github.com/pmndrs/valtio) and [Svelte Routing](https://github.com/EmilTholin/svelte-routing). -[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/vue-v0.0.1). +[**See the release notes for the 0.0.1 alpha release**](https://github.com/fastify/fastify-dx/releases/tag/svelte-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. @@ -72,7 +72,7 @@ It also includes some _**opinionated**_ essentials: - [**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. Svelte support is provided via [Sveltio](https://github.com/wobsoriano/sveltio) by [Robert Soriano](https://robsoriano.com/). +- [**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. <br>Svelte support is provided via [Sveltio](https://github.com/wobsoriano/sveltio) by [Robert Soriano](https://robsoriano.com/). ## Package Scripts diff --git a/packages/fastify-dx-svelte/index.js b/packages/fastify-dx-svelte/index.js index 6fad2a2..cb2a6f5 100644 --- a/packages/fastify-dx-svelte/index.js +++ b/packages/fastify-dx-svelte/index.js @@ -84,7 +84,7 @@ export function createHtmlFunction (source, scope, config) { ...context, style, head, - hydration + hydration, }), footer: footerTemplate(context), })) diff --git a/packages/fastify-dx-svelte/package.json b/packages/fastify-dx-svelte/package.json index 6169857..ddd070d 100644 --- a/packages/fastify-dx-svelte/package.json +++ b/packages/fastify-dx-svelte/package.json @@ -1,19 +1,19 @@ { "scripts": { - "lint": "eslint . --ext .js,.jsx --fix" + "lint": "eslint . --ext .js,.svelte --fix" }, "type": "module", "main": "index.js", "name": "fastify-dx-svelte", - "version": "0.0.1", + "version": "0.0.1-pre", "files": [ "virtual/root.svelte", + "virtual/route.svelte", "virtual/layouts.js", - "virtual/layouts/default.jsx", + "virtual/layouts/default.svelte", "virtual/context.js", "virtual/mount.js", - "virtual/resource.js", - "virtual/core.jsx", + "virtual/core.js", "virtual/routes.js", "index.js", "plugin.cjs", @@ -26,18 +26,19 @@ "./plugin": "./plugin.cjs" }, "dependencies": { - "svelte-routing": "^1.6.0", + "devalue": "^2.0.1", "svelte-loadable": "^2.0.1", + "svelte-routing": "^1.6.0", "sveltio": "^1.0.5", - "devalue": "^2.0.1", "unihead": "^0.0.6" }, "devDependencies": { - "eslint": "^7.32.0", + "@babel/eslint-parser": "^7.18.2", + "eslint": "^8.18.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-svelte3": "^4.0.0" } -} \ No newline at end of file +} diff --git a/packages/fastify-dx-svelte/plugin.cjs b/packages/fastify-dx-svelte/plugin.cjs index 6829d54..ecce70c 100644 --- a/packages/fastify-dx-svelte/plugin.cjs +++ b/packages/fastify-dx-svelte/plugin.cjs @@ -11,7 +11,6 @@ function viteSvelteFastifyDX (config = {}) { const virtualRoot = resolve(__dirname, 'virtual') const virtualModules = [ 'mount.js', - 'resource.js', 'routes.js', 'layouts.js', 'root.svelte', diff --git a/packages/fastify-dx-svelte/virtual/resource.js b/packages/fastify-dx-svelte/virtual/resource.js deleted file mode 100644 index 69d7310..0000000 --- a/packages/fastify-dx-svelte/virtual/resource.js +++ /dev/null @@ -1,68 +0,0 @@ - -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-svelte/virtual/route.svelte b/packages/fastify-dx-svelte/virtual/route.svelte index 684d7bf..978bbd2 100644 --- a/packages/fastify-dx-svelte/virtual/route.svelte +++ b/packages/fastify-dx-svelte/virtual/route.svelte @@ -9,7 +9,7 @@ const isServer = import.meta.env.SSR setContext(routeContext, { get routeContext () { return ctx - } + }, }) export let path From 32578cefc1da33edf169ebae689b312fba446804 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:56:00 -0300 Subject: [PATCH 19/20] fix --- packages/fastify-dx-svelte/index.js | 1 - packages/fastify-dx-svelte/package.json | 2 +- starters/react/client/pages/client-only.jsx | 16 +++++----- starters/svelte/.eslintrc | 24 +++++---------- starters/svelte/client/context.js | 4 +-- .../svelte/client/pages/client-only.svelte | 6 ++-- starters/svelte/client/pages/index.svelte | 2 +- .../svelte/client/pages/server-only.svelte | 14 ++++++--- .../svelte/client/pages/using-data.svelte | 2 +- starters/svelte/package.json | 30 ++++++++----------- starters/svelte/server.js | 2 +- starters/svelte/vite.config.js | 2 +- 12 files changed, 48 insertions(+), 57 deletions(-) diff --git a/packages/fastify-dx-svelte/index.js b/packages/fastify-dx-svelte/index.js index cb2a6f5..3d8239e 100644 --- a/packages/fastify-dx-svelte/index.js +++ b/packages/fastify-dx-svelte/index.js @@ -124,7 +124,6 @@ export function createRouteHandler (client, scope, config) { } export function createRoute ({ client, handler, errorHandler, route }, scope, config) { - console.log('route', route) const onRequest = async function onRequest (req, reply) { req.route = await RouteContext.create( scope, diff --git a/packages/fastify-dx-svelte/package.json b/packages/fastify-dx-svelte/package.json index ddd070d..d31f954 100644 --- a/packages/fastify-dx-svelte/package.json +++ b/packages/fastify-dx-svelte/package.json @@ -5,7 +5,7 @@ "type": "module", "main": "index.js", "name": "fastify-dx-svelte", - "version": "0.0.1-pre", + "version": "0.0.1", "files": [ "virtual/root.svelte", "virtual/route.svelte", diff --git a/starters/react/client/pages/client-only.jsx b/starters/react/client/pages/client-only.jsx index b55bf1d..2e72f69 100644 --- a/starters/react/client/pages/client-only.jsx +++ b/starters/react/client/pages/client-only.jsx @@ -3,21 +3,21 @@ import { Link } from 'react-router-dom' export const clientOnly = true export function getMeta () { - return { - title: 'Client Only Page' - } + return { + title: 'Client Only Page' + } } export default function ClientOnly () { - return ( - <> - <p>This route is rendered on the client only!</p> + return ( + <> + <p>This route is rendered on the client only!</p> <p> <Link to="/">Go back to the index</Link> </p> <p>⁂</p> <p>When this route is rendered on the server, no SSR takes place.</p> <p>See the output of <code>curl http://localhost:3000/client-only</code>.</p> - </> - ) + </> + ) } diff --git a/starters/svelte/.eslintrc b/starters/svelte/.eslintrc index be9e0ff..e5461ce 100644 --- a/starters/svelte/.eslintrc +++ b/starters/svelte/.eslintrc @@ -4,28 +4,18 @@ requireConfigFile: false, ecmaVersion: 2021, sourceType: 'module', - babelOptions: { - presets: ['@babel/preset-react'], - }, - ecmaFeatures: { - jsx: true, - }, }, - extends: [ - 'plugin:react/recommended', - 'standard', - ], plugins: [ - 'react', + 'svelte3' + ], + overrides: [ + { + files: ['*.svelte'], + processor: 'svelte3/svelte3' + } ], rules: { 'comma-dangle': ['error', 'always-multiline'], - 'react/prop-types': 'off', 'import/no-absolute-path': 'off', }, - settings: { - react: { - version: '18.0', - }, - }, } diff --git a/starters/svelte/client/context.js b/starters/svelte/client/context.js index acb9cf7..0b62748 100644 --- a/starters/svelte/client/context.js +++ b/starters/svelte/client/context.js @@ -7,7 +7,7 @@ export default (ctx) => { } export const $fetch = ky.extend({ - prefixUrl: 'http://localhost:3000' + prefixUrl: 'http://localhost:3000', }) export const state = () => ({ @@ -32,5 +32,5 @@ export const actions = { json: { index }, }) state.todoList.splice(index, 1) - } + }, } diff --git a/starters/svelte/client/pages/client-only.svelte b/starters/svelte/client/pages/client-only.svelte index 99ee045..0f01b14 100644 --- a/starters/svelte/client/pages/client-only.svelte +++ b/starters/svelte/client/pages/client-only.svelte @@ -2,9 +2,9 @@ export const clientOnly = true export function getMeta () { - return { - title: 'Client Only Page' - } + return { + title: 'Client Only Page', + } } </script> diff --git a/starters/svelte/client/pages/index.svelte b/starters/svelte/client/pages/index.svelte index 9347ebd..a6625f0 100644 --- a/starters/svelte/client/pages/index.svelte +++ b/starters/svelte/client/pages/index.svelte @@ -1,7 +1,7 @@ <script context="module"> export let getMeta = () => { return { - title: 'Welcome to Fastify DX!' + title: 'Welcome to Fastify DX!', } } </script> diff --git a/starters/svelte/client/pages/server-only.svelte b/starters/svelte/client/pages/server-only.svelte index cec05cc..9224a8a 100644 --- a/starters/svelte/client/pages/server-only.svelte +++ b/starters/svelte/client/pages/server-only.svelte @@ -1,9 +1,15 @@ -<script> -import { Link } from 'svelte-routing' -</script> - <script context="module"> export const serverOnly = true + +export function getMeta () { + return { + title: 'Server Only Page', + } +} +</script> + +<script> +import { Link } from 'svelte-routing' </script> <p>This route is rendered on the server only!</p> diff --git a/starters/svelte/client/pages/using-data.svelte b/starters/svelte/client/pages/using-data.svelte index 045bd90..959ba73 100644 --- a/starters/svelte/client/pages/using-data.svelte +++ b/starters/svelte/client/pages/using-data.svelte @@ -5,7 +5,7 @@ export let getMeta = () => { export let getData = ({ server }) => { return { - todoList: server.db.todoList + todoList: server.db.todoList, } } </script> diff --git a/starters/svelte/package.json b/starters/svelte/package.json index 3f5f031..ed691a3 100644 --- a/starters/svelte/package.json +++ b/starters/svelte/package.json @@ -7,38 +7,34 @@ "devinstall": "zx ../../devinstall.mjs svelte -- node server.js --dev", "build:client": "vite build --outDir dist/client --ssrManifest", "build:server": "vite build --outDir dist/server --ssr /index.js", - "lint": "eslint . --ext .js,.jsx --fix" + "lint": "eslint . --ext .js,.svelte --fix" }, "dependencies": { + "fastify-dx-svelte": "^0.0.1-pre", "fastify-vite": "^3.0.0-beta.23", - "ky-universal": "^0.10.1", - "svelte-routing": "^1.6.0", - "svelte-loadable": "^2.0.1", - "sveltio": "^1.0.5", - "devalue": "^2.0.1", - "unihead": "^0.0.6" + "ky": "^0.31.0", + "ky-universal": "^0.10.1" }, "devDependencies": { "@babel/eslint-parser": "^7.16.0", - "@babel/preset-react": "^7.16.0", "@sveltejs/vite-plugin-svelte": "^1.0.0-next.49", - "eslint": "^7.32.0", - "eslint-config-standard": "^16.0.2", + "eslint": "^8.18.0", + "eslint-config-standard": "^17.0.0", "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-promise": "^6.0.0", + "eslint-plugin-svelte3": "^4.0.0", "postcss-preset-env": "^7.7.1", - "unocss": "^0.37.4", - "vite-plugin-blueprint": "^0.0.4" + "unocss": "^0.37.4" }, "devInstall": { "local": { - "fastify-dx-svelte": "^0.0.1-alpha.0" + "fastify-dx-svelte": "^0.0.1" }, "external": { "fastify-vite": "^3.0.0-beta.23", - "ky-universal": "^0.10.1" + "ky-universal": "^0.10.1", + "ky": "^0.31.0" } } -} \ No newline at end of file +} diff --git a/starters/svelte/server.js b/starters/svelte/server.js index 5802c9e..1f5e588 100644 --- a/starters/svelte/server.js +++ b/starters/svelte/server.js @@ -9,7 +9,7 @@ server.decorate('db', { 'Do laundry', 'Respond to emails', 'Write report', - ] + ], }) server.put('/api/todo/items', (req, reply) => { diff --git a/starters/svelte/vite.config.js b/starters/svelte/vite.config.js index 6091229..e463ff1 100644 --- a/starters/svelte/vite.config.js +++ b/starters/svelte/vite.config.js @@ -14,7 +14,7 @@ const plugins = [ viteSvelte({ compilerOptions: { hydratable: true, - } + }, }), viteSvelteFastifyDX(), ] From 98c33903c3f74d07005effe30161fd0d1b664f07 Mon Sep 17 00:00:00 2001 From: Jonas Galvez <jonasgalvez@gmail.com> Date: Thu, 23 Jun 2022 22:56:44 -0300 Subject: [PATCH 20/20] update README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 864e267..45f439e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,23 @@ Try out the [**alpha release** of Fastify DX for Vue](https://github.com/fastify </table> +<table> +<tr> +<td width="200px" valign="top"> + +### [fastify-dx-svelte](https://github.com/fastify/fastify-dx/tree/main/packages/fastify-dx-svelte)<br><br>[![NPM version](https://img.shields.io/npm/v/fastify-dx-svelte.svg?style=flat)](https://www.npmjs.com/package/fastify-dx-svelte) + +</td> +<td width="500px"><br> + +Try out the [**alpha release** of Fastify DX for Svelte](https://github.com/fastify/fastify-dx/blob/main/packages/fastify-dx-svelte/README.md). + +</td> +</tr> +</table> + + + ## Status Fastify DX is currently in **alpha**.