feat: new architecture to support server framework plugin

- generic `server.serve` config
- new demo workspace
- new basic template
- updated readme

Reviewed-on: https://git.ayo.run/ayo/mcfly/pulls/3
Co-authored-by: Ayo <ayo@ayco.io>
Co-committed-by: Ayo <ayo@ayco.io>
This commit is contained in:
ayo 2026-06-04 11:17:09 +00:00 committed by ayo
parent 970970fd32
commit 6edab54354
47 changed files with 6961 additions and 2478 deletions

View file

@ -6,6 +6,6 @@
**/*.yml **/*.yml
**/*.yaml **/*.yaml
templates templates/**
**/public/* **/public/*

View file

@ -28,7 +28,7 @@ After setting up `git send-email` you can now follow the steps below to start ha
1⃣ Clone the repository to your local machine, then go into the project directory: 1⃣ Clone the repository to your local machine, then go into the project directory:
```bash ```bash
$ git clone https://git.sr.ht/~ayoayco/mcfly $ git clone https://git.ayo.run/ayo/mcfly
$ cd mcfly $ cd mcfly
``` ```

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly logo" /> <img width="250" src="https://git.ayo.run/ayo/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly logo" />
</p> </p>
<h1 align="center">McFly</h1> <h1 align="center">McFly</h1>

View file

@ -16,7 +16,7 @@ export default [
eslintPluginPrettierRecommended, eslintPluginPrettierRecommended,
includeIgnoreFile(gitignorePath), includeIgnoreFile(gitignorePath),
{ {
ignores: ['site/*', 'templates/*', '**/public/*', 'demo/*'], ignores: ['site/*', 'templates/**', '**/public/*', 'demo/*'],
}, },
{ {
rules: { rules: {

View file

@ -13,7 +13,6 @@
"build:site:preview": "pnpm -F site build:preview", "build:site:preview": "pnpm -F site build:preview",
"template:basic": "pnpm run build && pnpm -F basic-template start", "template:basic": "pnpm run build && pnpm -F basic-template start",
"create:mcfly": "node ./packages/create-mcfly", "create:mcfly": "node ./packages/create-mcfly",
"create:component": "node ./packages/create-component",
"cli": "node ./packages/core/cli/index.js", "cli": "node ./packages/core/cli/index.js",
"test": "vitest --run", "test": "vitest --run",
"lint": "eslint . --config eslint.config.mjs --cache", "lint": "eslint . --config eslint.config.mjs --cache",

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" /> <img width="250" src="https://git.ayo.run/ayo/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
</p> </p>
<h1 align="center">McFly</h1> <h1 align="center">McFly</h1>

View file

@ -1,6 +1,6 @@
{ {
"name": "@mcflyjs/config", "name": "@mcflyjs/config",
"version": "0.2.9", "version": "0.3.0-alpha",
"description": "Nitro configuration for McFly apps", "description": "Nitro configuration for McFly apps",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
@ -19,7 +19,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.sr.ht/~ayoayco/mcfly", "url": "https://git.ayo.run/ayo/mcfly",
"directory": "packages/config" "directory": "packages/config"
}, },
"author": "Ayo Ayco", "author": "Ayo Ayco",

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" /> <img width="250" src="https://git.ayo.run/ayo/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
</p> </p>
<h1 align="center">McFly</h1> <h1 align="center">McFly</h1>

View file

@ -1,6 +1,6 @@
{ {
"name": "@mcflyjs/core", "name": "@mcflyjs/core",
"version": "0.8.8", "version": "0.9.0-alpha",
"description": "McFly core package", "description": "McFly core package",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
@ -26,7 +26,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.sr.ht/~ayoayco/mcfly", "url": "https://git.ayo.run/ayo/mcfly",
"directory": "packages/core" "directory": "packages/core"
}, },
"author": "Ayo Ayco", "author": "Ayo Ayco",

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" /> <img width="250" src="https://git.ayo.run/ayo/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
</p> </p>
<h1 align="center">Create McFly</h1> <h1 align="center">Create McFly</h1>

View file

@ -7,15 +7,15 @@ import { spawnSync } from 'node:child_process'
import path from 'node:path' import path from 'node:path'
const [, , directoryArg] = process.argv const [, , directoryArg] = process.argv
/**
type PromptAction = { * @typedef {Object} PromptAction
prompt: string * @property {string} prompt - The prompt text to display
info?: string * @property {string} [info] - Additional information about the prompt
startMessage: string * @property {string} startMessage - Message to show when starting the action
command: string * @property {string} command - The command to execute
subCommand: string * @property {string} subCommand - The subcommand to execute
error: string * @property {string} error - Error message to display on failure
} */
/** /**
* Create McFly App * Create McFly App
@ -42,7 +42,10 @@ async function create() {
const hasErrors = await downloadTemplateToDirectory(directory) const hasErrors = await downloadTemplateToDirectory(directory)
if (!hasErrors) { if (!hasErrors) {
const prompts: PromptAction[] = [ /**
* @type {Array<PromptAction>}
*/
const prompts = [
{ {
prompt: `Would you like to install the dependencies to ${colorize( prompt: `Would you like to install the dependencies to ${colorize(
'bold', 'bold',
@ -80,7 +83,7 @@ async function create() {
* @param {string} directory * @param {string} directory
* @returns string * @returns string
*/ */
function getSafeDirectory(directory: string): string { function getSafeDirectory(directory) {
const { platform } = process const { platform } = process
const locale = path[platform === `win32` ? `win32` : `posix`] const locale = path[platform === `win32` ? `win32` : `posix`]
const localePath = directory.split(path.sep).join(locale.sep) const localePath = directory.split(path.sep).join(locale.sep)
@ -92,16 +95,14 @@ function getSafeDirectory(directory: string): string {
* @param {string} directory * @param {string} directory
* @returns Promise<boolean> hasErrors * @returns Promise<boolean> hasErrors
*/ */
async function downloadTemplateToDirectory( async function downloadTemplateToDirectory(directory) {
directory: string
): Promise<boolean> {
let hasErrors = false let hasErrors = false
try { try {
consola.start( consola.start(
`Copying template to ${colorize('bold', getSafeDirectory(directory))}...` `Copying template to ${colorize('bold', getSafeDirectory(directory))}...`
) )
await downloadTemplate('sourcehut:ayoayco/mcfly/templates/basic', { await downloadTemplate('github:ayo-run/mcfly/templates/basic', {
dir: directory, dir: directory,
}) })
} catch (_ㆆ) { } catch (_ㆆ) {
@ -123,11 +124,11 @@ async function downloadTemplateToDirectory(
* @param {string} cwd * @param {string} cwd
* @returns Promise<Array<boolean> | undefined> * @returns Promise<Array<boolean> | undefined>
*/ */
async function askPrompts( async function askPrompts(prompts, cwd) {
prompts: PromptAction[], /**
cwd: string * @type {Array<boolean>}
): Promise<boolean[] | undefined> { */
const results: boolean[] = [] const results = []
for (const p of prompts) { for (const p of prompts) {
const userIntends = await consola.prompt(p.prompt, { const userIntends = await consola.prompt(p.prompt, {
@ -169,7 +170,7 @@ async function askPrompts(
* @param {string} directory * @param {string} directory
* @param {boolean} installDeps * @param {boolean} installDeps
*/ */
function showResults(directory: string, installDeps: boolean) { function showResults(directory, installDeps) {
let nextActions = [ let nextActions = [
`Go to your project by running ${colorize('yellow', `cd ${directory}`)}`, `Go to your project by running ${colorize('yellow', `cd ${directory}`)}`,
] ]
@ -189,7 +190,7 @@ function showResults(directory: string, installDeps: boolean) {
'bold', 'bold',
'McFly' 'McFly'
)} app is ready: ${directory}\n\nNext actions: ${nextActions )} app is ready: ${directory}\n\nNext actions: ${nextActions
.map((action, index) => `\n${++index}. ${action}`) .map((action, index) => `\n${index}. ${action}`)
.join('')}` .join('')}`
consola.box(result) consola.box(result)

View file

@ -4,24 +4,22 @@
"description": "Create a new McFly app", "description": "Create a new McFly app",
"type": "module", "type": "module",
"bin": { "bin": {
"create-mcfly": "./dist/index.js" "create-mcfly": "./index.js"
}, },
"main": "./dist/index.js", "main": "./index.js",
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "default": "./index.js"
"default": "./dist/index.js"
} }
}, },
"scripts": { "scripts": {
"build": "tsc --erasableSyntaxOnly",
"version": "npm version", "version": "npm version",
"publish": "npm publish", "publish": "npm publish",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.sr.ht/~ayoayco/mcfly", "url": "https://git.ayo.run/ayo/mcfly",
"directory": "packages/create-mcfly" "directory": "packages/create-mcfly"
}, },
"author": "Ayo Ayco", "author": "Ayo Ayco",

View file

@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"allowJs": true,
"emitDeclarationOnly": false,
"declarationDir": "./dist",
"outDir": "./dist"
}
}

21
packages/fastify/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Ayo Ayco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,72 @@
<p align="center">
<img width="250" src="https://git.ayo.run/ayo/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
</p>
<h1 align="center">McFly Fastify Adapter</h1>
Use fastify as a server framework in McFly
```
npm create mcfly@latest
```
<p align="center"><strong>McFly</strong> is a no-framework framework<br />that assists in building on the Web</p>
<p align="center">
<img src="https://img.shields.io/badge/from-the_future-blue?style=flat" />
<img src="https://img.shields.io/badge/status-legit-purple?style=flat" />
<a href="https://mc-fly.vercel.app/demo" target="_blank"><img src="https://img.shields.io/badge/see-the_demo_↗-blue?style=flat&colorB=28CF8D" /></a>
</p>
## Features
The time has come for vanilla Web tech. 🎉
✅ Create web apps with vanilla custom elements<br>
✅ Write real .HTML files<br>
✅ Have no frameworks or reactivity libraries on the browser<br>
✅ Use server-side rendering<br>
✅ Deploy anywhere<br>
## Special directories
**1. `./src/pages/`**
- file-based routing for `.html` files
- directly use custom elements & static fragments (no imports or registry maintenance needed)
- use `<script server:setup>` to define logic that runs on the server, which then gets stripped away
**2. `./src/components/`**
- custom element constructor files (only `.js` files for now)
- all components are automatically registered using their file names; a `hello-world.js` component can be used as `<hello-world>`
- static `.html` fragments; a `my-header.html` fragment can be directly used as `<my-header>`
**3. `./src/api/`**
- file-based routing for REST API endpoints
- e.g., `./src/api/users.ts` can be accessed via `http://<domain>/api/users`
- TypeScript or JavaScript welcome!
## McFly config
To tell McFly you want to use components, pass the mode (only `"js"` for now) to the `components` prop mcfly.config.ts
```js
import defineConfig from './packages/define-config'
export default defineConfig({
components: 'js',
})
```
## More info
This framework is a result of [an exploration](https://social.ayco.io/@ayo/111195315785886977) for using [**Nitro**](https://nitro.unjs.io) and vanilla JS custom elements using a minimal [**Web Component Base**](https://WebComponent.io) class.
**Nitro** is the same production-grade web server powering [**Nuxt**](https://nuxt.com/)
---
_Just keep building_<br />
_A project by [Ayo](https://ayco.io)_

View file

@ -1,6 +1,6 @@
{ {
"name": "@mcflyjs/fastify", "name": "@mcflyjs/fastify",
"version": "0.0.1", "version": "0.1.0",
"type": "module", "type": "module",
"description": "", "description": "",
"main": "index.js", "main": "index.js",

File diff suppressed because it is too large Load diff

View file

@ -10,3 +10,7 @@ allowBuilds:
sharp: false sharp: false
unix-dgram: false unix-dgram: false
web-component-base: true web-component-base: true
minimumReleaseAgeExclude:
- '@mcflyjs/config@0.3.0-alpha'
- '@mcflyjs/core@0.9.0-alpha'
- '@mcflyjs/fastify@0.1.0'

View file

@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" /> <img width="250" src="https://git.ayo.run/ayo/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
</p> </p>
<h1 align="center">McFly Docs</h1> <h1 align="center">McFly Docs</h1>

View file

@ -6,7 +6,7 @@
"main": "index.js", "main": "index.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.sr.ht/~ayoayco/mcfly", "url": "https://git.ayo.run/ayo/mcfly",
"directory": "app" "directory": "app"
}, },
"author": "Ayo Ayco", "author": "Ayo Ayco",

View file

@ -30,7 +30,7 @@
<warning-block <warning-block
><span>This page is in-progress. See the ><span>This page is in-progress. See the
<a <a
href="https://git.sr.ht/~ayoayco/mcfly/tree/main/item/site/src/pages/demo.html" href="https://git.ayo.run/ayo/mcfly/tree/main/item/site/src/pages/demo.html"
>source code</a >source code</a
>.</span></warning-block >.</span></warning-block
> >

View file

@ -0,0 +1,6 @@
// @ts-check
import { defineMcFlyConfig } from '@mcflyjs/config'
export default defineMcFlyConfig({
components: 'js',
})

View file

@ -0,0 +1,16 @@
{
"name": "basic-legacy-template",
"description": "McFly starter project",
"scripts": {
"start": "mcfly serve",
"prepare": "mcfly prepare",
"dev": "mcfly serve",
"build": "mcfly build",
"preview": "node .output/server/index.mjs",
"build:preview": "npm run build && npm run preview"
},
"dependencies": {
"@mcflyjs/config": "^0.2.9",
"@mcflyjs/core": "^0.8.8"
}
}

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<!--
Hello! This page is an example McFly page.
See more on https://ayco.io/sh/mcfly
-->
<my-head>
<title>McFly: Back to the Basics. Into the Future.</title>
<script server:setup>
const project = {
name: "McFly",
description: "Back to the Basics. Into the Future."
};
</script>
</my-head>
<body>
<awesome-header>
<span>{{ project.name }}</span>
<span slot="description">{{ project.description }}</span>
</awesome-header>
<main>
<h2>Welcome to {{ project.name }}</h2>
<p>Server date time: {{new Date().toLocaleString()}}</p>
<p>
Here's an interactive custom element:
<my-hello-world></my-hello-world>
</p>
<code-block language="js">
class MyHelloWorld extends WebComponent {
static props = {
myName: &quot;World&quot;,
count: 0
}
updateLabel() {
this.props.myName = `Clicked ${++this.props.count}x`;
}
get template() {
return html`
&lt;button onClick=${() => this.updateLabel()} style="cursor:pointer"&gt;
Hello ${this.props.myName}!
&lt;/button&gt;`;
}
}</code-block>
</main>
<my-footer>
<small>
Learn how to build components easily at <a href="https://WebComponent.io">WebComponent.io</a>
</small>
</my-footer>
</body>
</html>

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -1,6 +1,6 @@
// @ts-check import { defineConfig } from '@mcflyjs/config'
import { defineMcFlyConfig } from '@mcflyjs/config' import fastify from '@mcflyjs/fastify'
export default defineMcFlyConfig({ export default defineConfig({
components: 'js', server: fastify(),
}) })

View file

@ -1,16 +1,17 @@
{ {
"name": "basic-template", "name": "mcfly-basic",
"description": "McFly starter project", "version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": { "scripts": {
"start": "mcfly serve", "test": "echo \"Error: no test specified\" && exit 1",
"prepare": "mcfly prepare", "start": "mcfly serve"
"dev": "mcfly serve",
"build": "mcfly build",
"preview": "node .output/server/index.mjs",
"build:preview": "npm run build && npm run preview"
}, },
"author": "",
"license": "ISC",
"dependencies": { "dependencies": {
"@mcflyjs/config": "^0.2.9", "@mcflyjs/config": "^0.3.0-alpha",
"@mcflyjs/core": "^0.8.8" "@mcflyjs/core": "^0.9.0-alpha",
"@mcflyjs/fastify": "^0.1"
} }
} }

View file

@ -0,0 +1,29 @@
# Routes Folder
Routes define the pathways within your application.
Fastify's structure supports the modular monolith approach, where your
application is organized into distinct, self-contained modules.
This facilitates easier scaling and future transition to a microservice architecture.
In the future you might want to independently deploy some of those.
In this folder you should define all the routes that define the endpoints
of your web application.
Each service is a [Fastify
plugin](https://fastify.dev/docs/latest/Reference/Plugins/), it is
encapsulated (it can have its own independent plugins) and it is
typically stored in a file; be careful to group your routes logically,
e.g. all `/users` routes in a `users.js` file. We have added
a `root.js` file for you with a '/' root added.
If a single file becomes too large, create a folder and add a `index.js` file there:
this file must be a Fastify plugin, and it will be loaded automatically
by the application. You can now add as many files as you want inside that folder.
In this way you can create complex routes within a single monolith,
and eventually extract them.
If you need to share functionality between routes, place that
functionality into the `plugins` folder, and share it via
[decorators](https://fastify.dev/docs/latest/Reference/Decorators/).
If you're a bit confused about using `async/await` to write routes, you would
better take a look at [Promise resolution](https://fastify.dev/docs/latest/Reference/Routes/#promise-resolution) for more details.

View file

@ -0,0 +1,6 @@
export default async (fastify) => {
fastify.get('/', async function (request, reply) {
console.log({ request, reply })
return 'This is an example'
})
}

View file

@ -0,0 +1,5 @@
export default async (fastify) => {
fastify.get('/', async function (request, reply) {
return 'This is the API Index'
})
}

View file

@ -0,0 +1,6 @@
export default async function (fastify, opts) {
fastify.get('/', async function (request, reply) {
console.log({ opts, request, reply })
return { root: true }
})
}

View file

@ -1,53 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<!-- <head>
Hello! This page is an example McFly page. <meta charset="UTF-8">
See more on https://ayco.io/sh/mcfly <meta name="viewport" content="width=device-width, initial-scale=1.0">
--> <title>WORK IN PROGRESS</title>
<my-head> </head>
<title>McFly: Back to the Basics. Into the Future.</title> <body>
<script server:setup> <h1>WORK IN PROGRESS</h1>
const project = { <p>See <a href="https://ayco.io/sh/mcfly">the project repository</a> for more info.</p>
name: "McFly", </body>
description: "Back to the Basics. Into the Future."
};
</script>
</my-head>
<body>
<awesome-header>
<span>{{ project.name }}</span>
<span slot="description">{{ project.description }}</span>
</awesome-header>
<main>
<h2>Welcome to {{ project.name }}</h2>
<p>Server date time: {{new Date().toLocaleString()}}</p>
<p>
Here's an interactive custom element:
<my-hello-world></my-hello-world>
</p>
<code-block language="js">
class MyHelloWorld extends WebComponent {
static props = {
myName: &quot;World&quot;,
count: 0
}
updateLabel() {
this.props.myName = `Clicked ${++this.props.count}x`;
}
get template() {
return html`
&lt;button onClick=${() => this.updateLabel()} style="cursor:pointer"&gt;
Hello ${this.props.myName}!
&lt;/button&gt;`;
}
}</code-block>
</main>
<my-footer>
<small>
Learn how to build components easily at <a href="https://WebComponent.io">WebComponent.io</a>
</small>
</my-footer>
</body>
</html> </html>