Compare commits
1 commit
main
...
new-conten
| Author | SHA1 | Date | |
|---|---|---|---|
| ce4c2a0716 |
38
.build.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
image: alpine/edge
|
||||||
|
packages:
|
||||||
|
- nodejs
|
||||||
|
- npm
|
||||||
|
- pnpm
|
||||||
|
secrets:
|
||||||
|
- 34883663-8684-41cb-9920-8e96345ef166
|
||||||
|
- bbfcb6dc-7c4a-42ee-a11a-022f0339a133
|
||||||
|
environment:
|
||||||
|
NETLIFY_SITE_ID: 390b392a-a898-491b-8500-79aa30f724d6
|
||||||
|
GH_USER: ayoayco
|
||||||
|
REPO: mcfly
|
||||||
|
tasks:
|
||||||
|
- push-mirror: |
|
||||||
|
cd ~/"${REPO}"
|
||||||
|
git config --global credential.helper store
|
||||||
|
git push --mirror "https://github.com/${GH_USER}/${REPO}"
|
||||||
|
- install-deps: |
|
||||||
|
cd ~/"${REPO}"
|
||||||
|
pnpm i --ignore-scripts
|
||||||
|
- test: |
|
||||||
|
cd ~/"${REPO}"
|
||||||
|
npx vitest run
|
||||||
|
- build: |
|
||||||
|
cd ~/"${REPO}"
|
||||||
|
pnpm -F ./packages/core build
|
||||||
|
pnpm -F ./packages/config build
|
||||||
|
pnpm -F site i --ignore-scripts
|
||||||
|
pnpm -F site build
|
||||||
|
- deploy: |
|
||||||
|
cd mcfly
|
||||||
|
{
|
||||||
|
set +x
|
||||||
|
. ~/.buildsecrets
|
||||||
|
set -x
|
||||||
|
}
|
||||||
|
export NETLIFY_AUTH_TOKEN
|
||||||
|
pnpm -F site run deploy
|
||||||
2
.gitignore
vendored
|
|
@ -16,6 +16,4 @@ html
|
||||||
*swp
|
*swp
|
||||||
*swo
|
*swo
|
||||||
|
|
||||||
# default scaffolded directories
|
|
||||||
mcfly-app
|
mcfly-app
|
||||||
hello-world
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
echo "post-commit..."
|
|
||||||
git push gh
|
|
||||||
git push sh
|
|
||||||
1
.husky/pre-commit
Executable file → Normal file
|
|
@ -1,2 +1 @@
|
||||||
echo "pre-commit..."
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|
@ -6,6 +6,4 @@
|
||||||
**/*.yml
|
**/*.yml
|
||||||
**/*.yaml
|
**/*.yaml
|
||||||
|
|
||||||
templates/**
|
templates
|
||||||
|
|
||||||
**/public/*
|
|
||||||
1
.urlview
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
BROWSER=firefox
|
||||||
1
.vscode/settings.json
vendored
|
|
@ -8,6 +8,7 @@
|
||||||
"hookable",
|
"hookable",
|
||||||
"mcfly",
|
"mcfly",
|
||||||
"mcflyjs",
|
"mcflyjs",
|
||||||
|
"nitropack",
|
||||||
"ultrahtml",
|
"ultrahtml",
|
||||||
"unstorage"
|
"unstorage"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
4
CHANGESET.md
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Rough list of changes
|
||||||
|
|
||||||
|
1. Expose a `McFly` object containing event properties to `server:setup` scripts. Useful for handling requests like form submission.
|
||||||
|
2. Add a cozy demo page for form handling using the exposed `McFly` object
|
||||||
|
|
@ -19,8 +19,51 @@ To start or participate on discussions, see our [mcfly-discussions](https://list
|
||||||
|
|
||||||
## Contribute Code
|
## Contribute Code
|
||||||
|
|
||||||
Due to still figuring out how parts fit together, we are not ready for code contributions.
|
We use `git` and `email` here -- it is actually fun!
|
||||||
|
|
||||||
|
To get started, setup [git send-email](https://git-send-email.io).
|
||||||
|
|
||||||
|
After setting up `git send-email` you can now follow the steps below to start hacking:
|
||||||
|
|
||||||
|
1️⃣ Clone the repository to your local machine, then go into the project directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone https://git.sr.ht/~ayoayco/mcfly
|
||||||
|
$ cd mcfly
|
||||||
|
```
|
||||||
|
|
||||||
|
2️⃣ Create a new branch for your changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git checkout -b my-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
3️⃣ Make your changes, and then commit them with a descriptive message using [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git commit -m "feat(core): implement server-side rendering"
|
||||||
|
```
|
||||||
|
|
||||||
|
4️⃣ Use `git send-email` to send a patch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git send-email --to="~ayoayco/mcfly-patches@lists.sr.ht" HEAD^
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tips:
|
||||||
|
|
||||||
|
💡 You can set the default "to" address for the project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git config sendemail.to "~ayoayco/mcfly-patches@lists.sr.ht"
|
||||||
|
```
|
||||||
|
|
||||||
|
💡 The `HEAD^` bit is a reference to the latest commit, which will be added to your patch. This could be a range of commits as well if you have mutiple commits.
|
||||||
|
|
||||||
|
5️⃣ After successfully sending your patch, wait for a response from us whether the patch needs rework... or a notification if it gets merged!
|
||||||
|
|
||||||
|
> As a summary, we use `git` and `email` to collaborate on McFly. You have to set up [git send-email](https://git-send-email.io) and send patches via email. :)
|
||||||
|
|
||||||
## Get in touch
|
## Get in touch
|
||||||
|
|
||||||
If something needs clarification, or even if you just want to chat about the project, don't hesitate to reach out to me via email [`hi@ayo.run`](mailto:hi@ayo.run). Thanks! :)
|
If something needs clarification, or even if you just want to chat about the project, don't hesitate to reach out to me via email [`ayo@ayco.io`](mailto:ayo@ayco.io). Thanks! :)
|
||||||
|
|
|
||||||
58
README.md
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="250" src="https://git.ayo.run/ayo/mcfly/raw/branch/main/assets/mcfly-logo-sm.png" alt="McFly logo" />
|
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="McFly logo" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">McFly</h1>
|
<h1 align="center">McFly</h1>
|
||||||
|
|
@ -11,8 +11,7 @@
|
||||||
<img src="https://img.shields.io/badge/status-legit-purple?style=flat" />
|
<img src="https://img.shields.io/badge/status-legit-purple?style=flat" />
|
||||||
<a href="https://mcfly.js.org/demo" target="_blank"><img src="https://img.shields.io/badge/see-the_demo_↗️-blue?style=flat&colorB=28CF8D" /></a>
|
<a href="https://mcfly.js.org/demo" target="_blank"><img src="https://img.shields.io/badge/see-the_demo_↗️-blue?style=flat&colorB=28CF8D" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
## Why
|
||||||
## Why?
|
|
||||||
|
|
||||||
I often wonder what it would look like to build sites & apps knowing just the basics.
|
I often wonder what it would look like to build sites & apps knowing just the basics.
|
||||||
|
|
||||||
|
|
@ -23,19 +22,13 @@ I thought:
|
||||||
- What if I can write [HTML fragments](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) and [assemble them](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) easily in a page?
|
- What if I can write [HTML fragments](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) and [assemble them](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) easily in a page?
|
||||||
- What if I did not have to worry about the [ever-growing number of ways to have a place on the Web](https://ayos.blog/places-in-the-web/)?
|
- What if I did not have to worry about the [ever-growing number of ways to have a place on the Web](https://ayos.blog/places-in-the-web/)?
|
||||||
|
|
||||||
**McFly** is my reimagination of web development: _Back to the basics. Into the future._
|
**McFly** is my reimagination of web development: *Back to the basics. Into the future.*
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
We are currently in a focused rewrite. All parts are subject to breaking changes in minor releases.
|
We are currently in a Proof of Concept phase. All parts are subject to breaking changes in minor releases.
|
||||||
|
|
||||||
- [x] generic plugin system for using server frameworks
|
👉 [Road to v1.0.0 todo items](https://github.com/ayoayco/McFly/issues?q=is%3Aissue%20state%3Aopen%20milestone%3Av1.0.0)
|
||||||
- [x] file-based API routing via fastify as server framework
|
|
||||||
- [ ] file-based HTML pages routing
|
|
||||||
- [ ] HTML templating via Eta
|
|
||||||
- [ ] auto-registry of custom elements
|
|
||||||
- [ ] SSR custom elements
|
|
||||||
- [ ] SSG builds
|
|
||||||
|
|
||||||
## Try it today
|
## Try it today
|
||||||
|
|
||||||
|
|
@ -45,7 +38,13 @@ Run the following to generate a McFly starter project.
|
||||||
npm create mcfly@latest
|
npm create mcfly@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## Target Features
|
## How it works (for the nerds)
|
||||||
|
|
||||||
|
It is primarily a runtime middleware for [Nitro](https://nitro.build). Every time a page is requested, the McFly middleware intercepts and assembles the view for the requestor. McFly does this with the assets it knows about which are mostly: pages, components, public assets.
|
||||||
|
|
||||||
|
The idea is to have a plugin system which allows for the core functionality to only "lean" on web platform features. Anything not yet a standard is implemented as a plugin which will be easily "swapped" away when the platform catches up.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
✅ Use vanilla custom elements (or sugar-coated web components)<br>
|
✅ Use vanilla custom elements (or sugar-coated web components)<br>
|
||||||
✅ Write server-powered .html pages<br>
|
✅ Write server-powered .html pages<br>
|
||||||
|
|
@ -55,7 +54,19 @@ npm create mcfly@latest
|
||||||
|
|
||||||
## Special directories
|
## Special directories
|
||||||
|
|
||||||
**1. `./src/api/`**
|
**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
|
- file-based routing for REST API endpoints
|
||||||
- e.g., `./src/api/users.js` can be accessed via `http://<domain>/api/users`
|
- e.g., `./src/api/users.js` can be accessed via `http://<domain>/api/users`
|
||||||
|
|
@ -64,12 +75,11 @@ npm create mcfly@latest
|
||||||
|
|
||||||
The following are the project packages published on the NPM registry:
|
The following are the project packages published on the NPM registry:
|
||||||
|
|
||||||
| Package | Description | Version |
|
| Package | Description | Version |
|
||||||
| :------------------------------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------- |
|
| :----------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------ |
|
||||||
| [`@mcflyjs/config`](https://ayco.io/n/@mcflyjs/config) | Configuration handling for McFly projects |  |
|
| [`@mcflyjs/config`](https://ayco.io/n/@mcflyjs/config) | Nitro server config for McFly projects |  |
|
||||||
| [`@mcflyjs/core`](https://ayco.io/n/@mcflyjs/core) | Commands & runtime handling |  |
|
| [`@mcflyjs/core`](https://ayco.io/n/@mcflyjs/core) | Route event and config handlers |  |
|
||||||
| [`@mcflyjs/fastify`](https://ayco.io/n/@mcflyjs/fastify) | Adapter for using fastify as server framework |  |
|
| [`create-mcfly`](https://ayco.io/n/create-mcfly) | Script for scaffolding a new McFly workspace |  |
|
||||||
| [`create-mcfly`](https://ayco.io/n/create-mcfly) | Script for scaffolding a new McFly workspace |  |
|
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
|
|
@ -91,11 +101,11 @@ The following commands are available to you on this project. Add more, or modify
|
||||||
|
|
||||||
## More info
|
## More info
|
||||||
|
|
||||||
This framework was initially a result of [an exploration](https://social.ayco.io/@ayo/111195315785886977) for using [Nitro](https://nitro.build) and custom elements using a minimal [Web Component Base](https://WebComponent.io) class.
|
This framework is a result of [an exploration](https://social.ayco.io/@ayo/111195315785886977) for using [Nitro](https://nitro.build) and custom elements using a minimal [Web Component Base](https://WebComponent.io) class.
|
||||||
|
|
||||||
In 2026, we pivoted to a new target architecture to become more like a "glue" that allows putting together existing options that achieve our goals. We avoid building custom mechanisms when we can. The resulting architecture theoretically allows using different server frameworks, templating libraries, custom element libraries, etc.
|
**Nitro** is the same production-grade web server powering [Nuxt](https://nuxt.com/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
_Just keep building._<br>
|
*Just keep building.*<br>
|
||||||
_A project by [Ayo](https://ayo.ayco.io)_
|
*A project by [Ayo](https://ayo.ayco.io)*
|
||||||
|
|
|
||||||
3
VALUES.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Values (initial)
|
||||||
|
|
||||||
|
Our core values include leaning on open standards and decentralized technologies, which do not require any form of lock-in. Therefore, you don’t need anything else than `git` and `email` to collaborate.
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { defineConfig } from '@mcflyjs/config'
|
|
||||||
import fastify from '@mcflyjs/fastify'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
server: fastify(),
|
|
||||||
})
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"name": "demo",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"start": "mcfly serve"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@mcflyjs/core": "workspace:*",
|
|
||||||
"@mcflyjs/config": "workspace:*",
|
|
||||||
"@mcflyjs/fastify": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export default async (fastify) => {
|
|
||||||
fastify.get('/', async function (request, reply) {
|
|
||||||
console.log({ request, reply })
|
|
||||||
return 'This is an example'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export default async (fastify) => {
|
|
||||||
fastify.get('/', async function (request, reply) {
|
|
||||||
return 'This is the API Index'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export default async function (fastify, opts) {
|
|
||||||
fastify.get('/', async function (request, reply) {
|
|
||||||
console.log({ opts, request, reply })
|
|
||||||
return { root: true }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default [
|
||||||
eslintPluginPrettierRecommended,
|
eslintPluginPrettierRecommended,
|
||||||
includeIgnoreFile(gitignorePath),
|
includeIgnoreFile(gitignorePath),
|
||||||
{
|
{
|
||||||
ignores: ['site/*', 'templates/**', '**/public/*', 'demo/*'],
|
ignores: ['site/*', 'templates/*'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
|
|
|
||||||
38
package.json
|
|
@ -2,48 +2,44 @@
|
||||||
"name": "mcfly-monorepo",
|
"name": "mcfly-monorepo",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "pnpm -F @mcflyjs/core build && pnpm -F @mcflyjs/config build",
|
"preinstall": "npx only-allow pnpm && pnpm -F @mcflyjs/core build && pnpm -F @mcflyjs/config build",
|
||||||
"start": "pnpm run site",
|
"start": "pnpm run site",
|
||||||
"dev": "pnpm run site",
|
"dev": "pnpm run site",
|
||||||
"demo": "pnpm run build:deps && pnpm -F demo start",
|
"site": "pnpm -F @mcflyjs/core run build && pnpm -F @mcflyjs/config run build && pnpm --filter site start",
|
||||||
"site": "pnpm run build:deps && pnpm -F site start",
|
|
||||||
"build": "pnpm -F './packages/**' build",
|
"build": "pnpm -F './packages/**' build",
|
||||||
"build:deps": "pnpm -F @mcflyjs/core build && pnpm -F @mcflyjs/config build",
|
|
||||||
"build:site": "pnpm -F site build",
|
"build:site": "pnpm -F site build",
|
||||||
"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": "node ./packages/create-mcfly",
|
||||||
"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",
|
||||||
"lint:fix": "eslint . --config eslint.config.mjs --fix",
|
|
||||||
"check": "npm run format && npm run lint",
|
"check": "npm run format && npm run lint",
|
||||||
"format": "prettier . --write",
|
"format": "prettier . --write",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^2.0.5",
|
"@eslint/compat": "^1.2.7",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^9.23.0",
|
||||||
"@types/node": "^25.6.0",
|
"eslint": "^9.23.0",
|
||||||
"eslint": "^10.2.1",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-prettier": "^5.5.5",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"globals": "^17.5.0",
|
"globals": "^16.0.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"netlify-cli": "^25.6.0",
|
"netlify-cli": "^19.0.3",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.5.3",
|
||||||
"tsup": "^8.5.1",
|
"tsup": "^8.4.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^5.8.2",
|
||||||
"vitest": "^4.1.5"
|
"vitest": "^3.0.9"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,ts,mjs,cjs,json,.*rc}": [
|
"*.{js,ts,mjs,cjs,json,.*rc}": [
|
||||||
"npm run format",
|
"npx prettier --write",
|
||||||
"npm run lint:fix"
|
"npx eslint --fix"
|
||||||
],
|
],
|
||||||
"*.json": [
|
"*.json": [
|
||||||
"npm run format"
|
"npx prettier --write"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="250" src="https://git.ayo.run/ayo/mcfly/raw/branch/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
|
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">McFly</h1>
|
<h1 align="center">McFly</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mcflyjs/config",
|
"name": "@mcflyjs/config",
|
||||||
"version": "0.3.0-alpha",
|
"version": "0.2.9",
|
||||||
"description": "Nitro configuration for McFly apps",
|
"description": "Nitro configuration for McFly apps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"default": "./dist/index.js"
|
"default": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"./define-config": "./dist/define-config.js"
|
"./define-mcfly-config": "./dist/define-mcfly-config.js",
|
||||||
|
"./nitro-config": "./dist/nitro-config.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc --erasableSyntaxOnly",
|
"build": "tsc --erasableSyntaxOnly",
|
||||||
|
|
@ -19,7 +20,7 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.ayo.run/ayo/mcfly",
|
"url": "https://git.sr.ht/~ayoayco/mcfly",
|
||||||
"directory": "packages/config"
|
"directory": "packages/config"
|
||||||
},
|
},
|
||||||
"author": "Ayo Ayco",
|
"author": "Ayo Ayco",
|
||||||
|
|
@ -31,5 +32,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"h3": "^1.15.1",
|
"h3": "^1.15.1",
|
||||||
"web-component-base": "^4.0.0"
|
"web-component-base": "^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nitropack": "~2.11.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import type { NitroConfig } from 'nitropack'
|
||||||
|
|
||||||
export type McFlyConfig = {
|
export type McFlyConfig = {
|
||||||
server: McFlyServer
|
components: 'js' | 'lit'
|
||||||
components?: 'js' | 'lit'
|
nitro?: NitroConfig
|
||||||
plugins?: McFlyPlugin[]
|
plugins?: McFlyPlugin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -9,7 +11,7 @@ export type McFlyConfig = {
|
||||||
* @param {McFlyConfig} config
|
* @param {McFlyConfig} config
|
||||||
* @returns {function(): McFlyConfig}e
|
* @returns {function(): McFlyConfig}e
|
||||||
*/
|
*/
|
||||||
export function defineConfig(config: McFlyConfig) {
|
export function defineMcFlyConfig(config: McFlyConfig) {
|
||||||
return () => config
|
return () => config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,5 +19,3 @@ export function defineConfig(config: McFlyConfig) {
|
||||||
* TODO: finalize Plugin type
|
* TODO: finalize Plugin type
|
||||||
*/
|
*/
|
||||||
export type McFlyPlugin = {}
|
export type McFlyPlugin = {}
|
||||||
|
|
||||||
export type McFlyServer = any
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export { type McFlyConfig, defineConfig } from './define-config.js'
|
export { type McFlyConfig, defineMcFlyConfig } from './define-mcfly-config.js'
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"emitDeclarationOnly": false,
|
"emitDeclarationOnly": false,
|
||||||
"declarationDir": "./dist",
|
"declarationDir": "./dist",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist"
|
||||||
"rootDir": "./src"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2026 Ayo Ayco
|
Copyright (c) 2023 Ayo Ayco
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
88
packages/content/README.md
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<p align="center">
|
||||||
|
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">McFly Content</h1>
|
||||||
|
|
||||||
|
<p align="center"><strong>McFly</strong> is a no-framework metaframework<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://mcfly.js.org/demo" target="_blank"><img src="https://img.shields.io/badge/see-the_demo_↗️-blue?style=flat&colorB=28CF8D" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
I often wonder what it would look like to build sites & apps knowing just the basics.
|
||||||
|
|
||||||
|
I thought:
|
||||||
|
|
||||||
|
- What if I knew how to write [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) and I can have a dynamic web page from that?
|
||||||
|
- What if I knew how to build [custom elements](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) and that's all the component system I needed?
|
||||||
|
- What if I can write [HTML fragments](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) and [assemble them](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) easily in a page?
|
||||||
|
- What if I did not have to worry about the [ever-growing number of ways to have a place on the Web](https://ayos.blog/places-in-the-web/)?
|
||||||
|
|
||||||
|
**McFly** is my reimagination of web development: Back to the basics. Into the future.
|
||||||
|
|
||||||
|
## Project Status
|
||||||
|
|
||||||
|
We are currently in a Proof of Concept phase. All parts are subject to breaking changes in minor releases.
|
||||||
|
|
||||||
|
## Try it today
|
||||||
|
|
||||||
|
Run the following to generate a McFly starter project.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm create mcfly@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
✅ Use vanilla custom elements (or sugar-coated web components)<br>
|
||||||
|
✅ Write server-powered .html pages<br>
|
||||||
|
✅ "Use the Platform™" on both server runtimes & browsers<br>
|
||||||
|
✅ Use server-side rendering<br>
|
||||||
|
✅ Deploy anywhere, even the Edge<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!
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
|
The following are the project packages published on the NPM registry:
|
||||||
|
|
||||||
|
| Package | Description | Version |
|
||||||
|
| ------------------------------------------------------ | -------------------------------------------- | ---------------------------------------------------------------- |
|
||||||
|
| [`@mcflyjs/cli`](https://ayco.io/n/@mcflyjs/cli) | The McFly CLI tooling |  |
|
||||||
|
| [`@mcflyjs/config`](https://ayco.io/n/@mcflyjs/config) | Nitro server config for McFly projects |  |
|
||||||
|
| [`@mcflyjs/core`](https://ayco.io/n/@mcflyjs/core) | Route event and config handlers |  |
|
||||||
|
| [`create-mcfly`](https://ayco.io/n/create-mcfly) | Script for scaffolding a new McFly workspace |  |
|
||||||
|
|
||||||
|
## 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)_
|
||||||
3
packages/content/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export default () => {
|
||||||
|
console.log('>>> content plugin')
|
||||||
|
}
|
||||||
11
packages/content/package.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "@mcflyjs/content",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "McFly plugin for static content generation",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Ayo Ayco",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="250" src="https://git.ayo.run/ayo/mcfly/raw/branch/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
|
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">McFly</h1>
|
<h1 align="center">McFly</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "@mcflyjs/core",
|
"name": "@mcflyjs/core",
|
||||||
"version": "0.9.0-alpha",
|
"version": "0.8.8",
|
||||||
"description": "McFly core package",
|
"description": "McFly core package",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/cli/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mcfly": "./dist/index.js"
|
"mcfly": "./dist/cli/index.js"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/cli/index.d.ts",
|
||||||
"default": "./dist/index.js"
|
"default": "./dist/cli/index.js"
|
||||||
},
|
},
|
||||||
"./runtime": "./dist/runtime/index.js",
|
"./runtime": "./dist/runtime/index.js",
|
||||||
|
"./cli": "./dist/cli/index.js",
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
@ -26,7 +27,7 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.ayo.run/ayo/mcfly",
|
"url": "https://git.sr.ht/~ayoayco/mcfly",
|
||||||
"directory": "packages/core"
|
"directory": "packages/core"
|
||||||
},
|
},
|
||||||
"author": "Ayo Ayco",
|
"author": "Ayo Ayco",
|
||||||
|
|
@ -42,6 +43,7 @@
|
||||||
"devalue": "^5.1.1",
|
"devalue": "^5.1.1",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
"h3": "^1.15.1",
|
"h3": "^1.15.1",
|
||||||
|
"nitropack": "^2.11.7",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
"ultrahtml": "^1.5.3"
|
"ultrahtml": "^1.5.3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,15 @@
|
||||||
import { consola } from 'consola'
|
import { consola } from 'consola'
|
||||||
import { defineCommand, type ParsedArgs } from 'citty'
|
import { defineCommand, type ParsedArgs } from 'citty'
|
||||||
import { dirname, resolve } from 'pathe'
|
import { dirname, resolve } from 'pathe'
|
||||||
|
import {
|
||||||
|
build,
|
||||||
|
copyPublicAssets,
|
||||||
|
createNitro,
|
||||||
|
prepare,
|
||||||
|
prerender,
|
||||||
|
} from 'nitropack'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { getMcFlyConfig } from '../get-config.js'
|
import { getMcFlyConfig, getNitroConfig } from '../../get-config.js'
|
||||||
|
|
||||||
async function _build(args: ParsedArgs) {
|
async function _build(args: ParsedArgs) {
|
||||||
consola.start('Building project...')
|
consola.start('Building project...')
|
||||||
|
|
@ -14,11 +21,33 @@ async function _build(args: ParsedArgs) {
|
||||||
const rootDir = resolve(dir)
|
const rootDir = resolve(dir)
|
||||||
|
|
||||||
const { mcflyConfig, configFile } = await getMcFlyConfig()
|
const { mcflyConfig, configFile } = await getMcFlyConfig()
|
||||||
|
const nitroConfig = await getNitroConfig(mcflyConfig)
|
||||||
|
|
||||||
|
const nitro = await createNitro({
|
||||||
|
rootDir,
|
||||||
|
dev: false,
|
||||||
|
|
||||||
|
...nitroConfig,
|
||||||
|
|
||||||
|
minify: args.minify ?? nitroConfig.minify,
|
||||||
|
preset: args.preset ?? nitroConfig.preset,
|
||||||
|
})
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = dirname(__filename)
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
consola.info('dir', __dirname)
|
nitro.options.handlers.push({
|
||||||
|
middleware: true,
|
||||||
|
handler: resolve(__dirname, '../../route-middleware.js'),
|
||||||
|
})
|
||||||
|
|
||||||
|
nitro.options.runtimeConfig.appConfigFile = configFile
|
||||||
|
|
||||||
|
await prepare(nitro)
|
||||||
|
await copyPublicAssets(nitro)
|
||||||
|
await prerender(nitro)
|
||||||
|
await build(nitro)
|
||||||
|
await nitro.close()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
consola.error(err)
|
consola.error(err)
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
import { consola } from 'consola'
|
import { consola } from 'consola'
|
||||||
import { defineCommand, type ParsedArgs } from 'citty'
|
import { defineCommand, type ParsedArgs } from 'citty'
|
||||||
import { resolve } from 'pathe'
|
import { resolve } from 'pathe'
|
||||||
import { getMcFlyConfig } from '../get-config.js'
|
import { createNitro, writeTypes } from 'nitropack'
|
||||||
|
import { getMcFlyConfig, getNitroConfig } from '../../get-config.js'
|
||||||
|
|
||||||
async function prepare(args: ParsedArgs) {
|
async function prepare(args: ParsedArgs) {
|
||||||
consola.start('Preparing McFly workspace...')
|
consola.start('Preparing McFly workspace...')
|
||||||
|
|
@ -15,8 +16,10 @@ async function prepare(args: ParsedArgs) {
|
||||||
const dir: string = args.dir?.toString() || args._dir?.toString() || '.'
|
const dir: string = args.dir?.toString() || args._dir?.toString() || '.'
|
||||||
const rootDir = resolve(dir)
|
const rootDir = resolve(dir)
|
||||||
const { mcflyConfig } = await getMcFlyConfig()
|
const { mcflyConfig } = await getMcFlyConfig()
|
||||||
|
const nitroConfig = await getNitroConfig(mcflyConfig)
|
||||||
|
const nitro = await createNitro({ rootDir, ...nitroConfig })
|
||||||
|
|
||||||
consola.info({ mcflyConfig, rootDir })
|
await writeTypes(nitro)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
consola.error(e)
|
consola.error(e)
|
||||||
err = e
|
err = e
|
||||||
129
packages/core/src/cli/commands/serve.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { consola } from 'consola'
|
||||||
|
import { colorize } from 'consola/utils'
|
||||||
|
import { defineCommand, type ParsedArgs } from 'citty'
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import {
|
||||||
|
type Nitro,
|
||||||
|
build,
|
||||||
|
createDevServer,
|
||||||
|
createNitro,
|
||||||
|
prepare,
|
||||||
|
prerender,
|
||||||
|
} from 'nitropack'
|
||||||
|
import { resolve } from 'pathe'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { dirname } from 'pathe'
|
||||||
|
import { getMcFlyConfig, getNitroConfig } from '../../get-config.js'
|
||||||
|
|
||||||
|
const hmrKeyRe = /^runtimeConfig\.|routeRules\./
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
|
||||||
|
async function printInfo() {
|
||||||
|
try {
|
||||||
|
const _require = createRequire(import.meta.url)
|
||||||
|
const mcflyPkg = await _require('@mcflyjs/core/package.json')
|
||||||
|
const mcflyPkgVersion = `McFly ${colorize('bold', mcflyPkg.version)}`
|
||||||
|
const nitroPkg = await _require('nitropack/package.json')
|
||||||
|
const nitroPkgVersion = `Nitro ${nitroPkg.version}`
|
||||||
|
consola.log(
|
||||||
|
`${colorize('blue', mcflyPkgVersion)} ${colorize('dim', nitroPkgVersion)}`
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
consola.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function serve(args: ParsedArgs) {
|
||||||
|
try {
|
||||||
|
// TODO: check for dir type (should be string)
|
||||||
|
const dir = args.dir?.toString() || args._dir?.toString()
|
||||||
|
const rootDir: string = resolve(dir || '.')
|
||||||
|
|
||||||
|
let nitro: Nitro
|
||||||
|
const reload = async () => {
|
||||||
|
// close existing nitro
|
||||||
|
if (nitro) {
|
||||||
|
consola.info('Restarting dev server...')
|
||||||
|
if ('unwatch' in nitro.options._c12) {
|
||||||
|
await nitro.options._c12.unwatch()
|
||||||
|
}
|
||||||
|
await nitro.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mcflyConfig, configFile } = await getMcFlyConfig()
|
||||||
|
const nitroConfig = await getNitroConfig(mcflyConfig)
|
||||||
|
|
||||||
|
// create new nitro
|
||||||
|
nitro = await createNitro(
|
||||||
|
{
|
||||||
|
rootDir,
|
||||||
|
dev: true,
|
||||||
|
preset: 'nitro-dev',
|
||||||
|
_cli: { command: 'dev' },
|
||||||
|
...nitroConfig,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
watch: true,
|
||||||
|
c12: {
|
||||||
|
async onUpdate({ getDiff, newConfig }: unknown) {
|
||||||
|
const diff = getDiff()
|
||||||
|
|
||||||
|
if (diff.length === 0) {
|
||||||
|
return // No changes
|
||||||
|
}
|
||||||
|
|
||||||
|
consola.info(
|
||||||
|
'Nitro config updated:\n' +
|
||||||
|
diff
|
||||||
|
.map((entry: unknown) => ` ${entry?.toString()}`)
|
||||||
|
.join('\n')
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: get types for c12 config & remove unknown
|
||||||
|
// @ts-ignore
|
||||||
|
await (diff.every((e: unknown) => hmrKeyRe.test(e.key))
|
||||||
|
? nitro.updateConfig(newConfig.config || {}) // Hot reload
|
||||||
|
: reload()) // Full reload
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
nitro.hooks.hookOnce('restart', reload)
|
||||||
|
nitro.options.runtimeConfig.appConfigFile = configFile
|
||||||
|
|
||||||
|
nitro.options.handlers.push({
|
||||||
|
middleware: true,
|
||||||
|
handler: resolve(__dirname, '../../route-middleware.js'),
|
||||||
|
})
|
||||||
|
const server = createDevServer(nitro)
|
||||||
|
|
||||||
|
// const listenOptions = parseArgs(args)
|
||||||
|
await server.listen(1234)
|
||||||
|
await prepare(nitro)
|
||||||
|
await prerender(nitro)
|
||||||
|
await build(nitro)
|
||||||
|
}
|
||||||
|
await reload()
|
||||||
|
} catch (e) {
|
||||||
|
consola.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineCommand({
|
||||||
|
meta: {
|
||||||
|
name: 'serve',
|
||||||
|
description: 'Runs the dev server.',
|
||||||
|
},
|
||||||
|
async run({ args }) {
|
||||||
|
await printInfo()
|
||||||
|
await serve(args)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const exportedForTest = {
|
||||||
|
serve,
|
||||||
|
printInfo,
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { defineCommand, runMain, type ArgsDef, type CommandDef } from 'citty'
|
import { defineCommand, runMain, type ArgsDef, type CommandDef } from 'citty'
|
||||||
|
|
||||||
export type Logger = {
|
|
||||||
log: Function
|
|
||||||
error: Function
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ServerConfig = {
|
|
||||||
rootDir: string
|
|
||||||
apiDir: string
|
|
||||||
port: number
|
|
||||||
logger: Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
const main: CommandDef<ArgsDef> = defineCommand({
|
const main: CommandDef<ArgsDef> = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
name: 'mcfly',
|
name: 'mcfly',
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
import { consola } from 'consola'
|
|
||||||
import { colorize } from 'consola/utils'
|
|
||||||
import { defineCommand, type ParsedArgs } from 'citty'
|
|
||||||
import { createRequire } from 'node:module'
|
|
||||||
import { resolve } from 'pathe'
|
|
||||||
// import { fileURLToPath } from 'node:url'
|
|
||||||
// import { dirname } from 'pathe'
|
|
||||||
import { getMcFlyConfig } from '../get-config.js'
|
|
||||||
|
|
||||||
// const __filename = fileURLToPath(import.meta.url)
|
|
||||||
// const __dirname = dirname(__filename)
|
|
||||||
|
|
||||||
async function printInfo() {
|
|
||||||
try {
|
|
||||||
const _require = createRequire(import.meta.url)
|
|
||||||
const mcflyPkg = await _require('@mcflyjs/core/package.json')
|
|
||||||
const mcflyPkgVersion = `McFly ${colorize('bold', mcflyPkg.version)}`
|
|
||||||
consola.log(`${colorize('blue', mcflyPkgVersion)}`)
|
|
||||||
} catch (e) {
|
|
||||||
consola.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function serve(args: ParsedArgs) {
|
|
||||||
try {
|
|
||||||
// TODO: check for dir type (should be string)
|
|
||||||
const dir = args.dir?.toString() || args._dir?.toString()
|
|
||||||
const rootDir: string = resolve(dir || '.')
|
|
||||||
|
|
||||||
const { mcflyConfig } = await getMcFlyConfig()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: config
|
|
||||||
* - separate `start` & `dev` commands
|
|
||||||
* - srcDir, apiDir ?
|
|
||||||
* - autoLoad config
|
|
||||||
* - fastify config
|
|
||||||
*/
|
|
||||||
if (mcflyConfig.server)
|
|
||||||
mcflyConfig.server.serve({
|
|
||||||
rootDir: rootDir + '/src',
|
|
||||||
apiDir: '/api',
|
|
||||||
logger: consola,
|
|
||||||
})
|
|
||||||
else consola.error('[McFly]: `server` configuration required')
|
|
||||||
} catch (e) {
|
|
||||||
consola.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineCommand({
|
|
||||||
meta: {
|
|
||||||
name: 'serve',
|
|
||||||
description: 'Runs the dev server.',
|
|
||||||
},
|
|
||||||
async run({ args }) {
|
|
||||||
await printInfo()
|
|
||||||
await serve(args)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const exportedForTest = {
|
|
||||||
serve,
|
|
||||||
printInfo,
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,27 @@
|
||||||
import { loadConfig } from 'c12'
|
import { loadConfig } from 'c12'
|
||||||
|
import { mcflyNitroConfig } from './mcfly-nitro-config.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('nitropack').NitroConfig} NitroConfig
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a valid Nitro configuration given a McFly config object
|
||||||
|
* @returns {Promise<NitroConfig>}
|
||||||
|
*/
|
||||||
|
export async function getNitroConfig(mcflyConfig = {}) {
|
||||||
|
const { config: nitroConfig } = await loadConfig({ name: 'nitro' })
|
||||||
|
return {
|
||||||
|
// nitro config in mcfly config
|
||||||
|
...mcflyConfig.nitro,
|
||||||
|
|
||||||
|
// nitro config from nitro config
|
||||||
|
...(nitroConfig ?? {}),
|
||||||
|
|
||||||
|
// McFly standard nitro config
|
||||||
|
...mcflyNitroConfig,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getMcFlyConfig() {
|
export async function getMcFlyConfig() {
|
||||||
const { config: mcflyConfig, configFile } = await loadConfig({
|
const { config: mcflyConfig, configFile } = await loadConfig({
|
||||||
|
|
|
||||||
35
packages/core/src/mcfly-nitro-config.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { type NitroConfig } from 'nitropack'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('nitropack').NitroConfig} NitroConfig
|
||||||
|
* @type {NitroConfig}
|
||||||
|
*/
|
||||||
|
export const mcflyNitroConfig: NitroConfig = {
|
||||||
|
framework: {
|
||||||
|
name: 'McFly',
|
||||||
|
},
|
||||||
|
compatibilityDate: '2024-12-08',
|
||||||
|
srcDir: 'src',
|
||||||
|
apiDir: 'api',
|
||||||
|
devServer: {
|
||||||
|
watch: ['./pages', './components', './api'],
|
||||||
|
},
|
||||||
|
serverAssets: [
|
||||||
|
{
|
||||||
|
baseName: 'pages',
|
||||||
|
dir: './pages',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
baseName: 'components',
|
||||||
|
dir: './components',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imports: {
|
||||||
|
presets: [
|
||||||
|
{
|
||||||
|
from: 'web-component-base',
|
||||||
|
imports: ['WebComponent', 'html', 'attachEffect'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
161
packages/core/src/route-middleware.js
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
import { eventHandler } from 'h3'
|
||||||
|
import { useStorage } from 'nitropack/runtime'
|
||||||
|
import { createHooks } from 'hookable'
|
||||||
|
import { consola } from 'consola'
|
||||||
|
import { colorize } from 'consola/utils'
|
||||||
|
import { useRuntimeConfig } from 'nitropack/runtime'
|
||||||
|
import { dirname, relative } from 'pathe'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
import {
|
||||||
|
hooks as mcflyHooks,
|
||||||
|
defaultMcflyConfig,
|
||||||
|
evaluateServerScripts,
|
||||||
|
injectHtmlFragments,
|
||||||
|
injectCustomElements,
|
||||||
|
} from '@mcflyjs/core/runtime' // important to import from installed node_module because this script is passed to another context
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../config').McFlyConfig} Config
|
||||||
|
* @typedef {import('unstorage').Storage} Storage
|
||||||
|
* @typedef {import('unstorage').StorageValue} StorageValue
|
||||||
|
* @typedef {import('h3').EventHandler} EventHandler
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* McFly middleware event handler
|
||||||
|
*/
|
||||||
|
export default eventHandler(async (event) => {
|
||||||
|
const timeStart = performance.now()
|
||||||
|
const hooks = createHooks()
|
||||||
|
Object.keys(mcflyHooks).forEach((hookName) => hooks.addHooks(hookName))
|
||||||
|
const { path } = event
|
||||||
|
const storage = useStorage()
|
||||||
|
|
||||||
|
const { appConfigFile } = useRuntimeConfig()
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = dirname(__filename)
|
||||||
|
let relativePath = relative(__dirname, appConfigFile)
|
||||||
|
|
||||||
|
let config
|
||||||
|
// TODO: this still doesn't work on Netlify
|
||||||
|
try {
|
||||||
|
const { default: configFn } = await import(relativePath)
|
||||||
|
config = configFn()
|
||||||
|
} catch (err) {
|
||||||
|
consola.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not page, don't render
|
||||||
|
if (event.path.startsWith('/api')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config || Object.keys(config).length === 0) {
|
||||||
|
config = defaultMcflyConfig
|
||||||
|
consola.warn(
|
||||||
|
`[WARN]: McFly configuration not found, using defaults...`,
|
||||||
|
defaultMcflyConfig
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const plugins = config.plugins ?? []
|
||||||
|
|
||||||
|
plugins.forEach((plugin) => {
|
||||||
|
const pluginHooks = Object.keys(plugin)
|
||||||
|
pluginHooks.forEach((pluginHook) => {
|
||||||
|
hooks.hook(pluginHook, plugin[pluginHook])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const { components: componentType } = config
|
||||||
|
let html = await getHtml(path, storage)
|
||||||
|
|
||||||
|
if (html) {
|
||||||
|
const transforms = [
|
||||||
|
{
|
||||||
|
fn: evaluateServerScripts,
|
||||||
|
args: [event],
|
||||||
|
hook: mcflyHooks.serverScriptsEvaluated,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: injectHtmlFragments,
|
||||||
|
args: [storage],
|
||||||
|
hook: mcflyHooks.fragmentsInjected,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fn: injectCustomElements,
|
||||||
|
args: [componentType, storage],
|
||||||
|
hook: mcflyHooks.customElementsInjected,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
if (!!componentType && !!html) {
|
||||||
|
for (const transform of transforms) {
|
||||||
|
html = await transform.fn(html.toString(), ...transform.args)
|
||||||
|
|
||||||
|
// call hook
|
||||||
|
if (transform.hook) {
|
||||||
|
// not sure if we want to await, for now it makes the outcome predictable
|
||||||
|
await hooks.callHook(transform.hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
consola.error('[ERR]: Failed to insert registry', {
|
||||||
|
componentType: !componentType ? 'missing' : 'okay',
|
||||||
|
html: !html ? 'missing' : 'okay',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (html) {
|
||||||
|
await hooks.callHook(mcflyHooks.pageRendered)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeEnd = performance.now()
|
||||||
|
consola.info(
|
||||||
|
colorize('green', event.path),
|
||||||
|
'rendered in',
|
||||||
|
Math.round(timeEnd - timeStart),
|
||||||
|
'ms'
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
html ??
|
||||||
|
new Response(
|
||||||
|
'😱 ERROR 404: Not found. You can put a 404.html on the ./src/pages directory to customize this error page.',
|
||||||
|
{ status: 404 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Gets the storage path for a file
|
||||||
|
* @param {string} filename
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getPath(filename) {
|
||||||
|
return `assets:pages${filename}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPurePath(path) {
|
||||||
|
return path.split('?')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the correct HTML depending on the path requested
|
||||||
|
* @param {string} path
|
||||||
|
* @param {Storage} storage
|
||||||
|
* @returns {Promise<StorageValue>}
|
||||||
|
*/
|
||||||
|
async function getHtml(path, storage) {
|
||||||
|
const purePath = getPurePath(path)
|
||||||
|
const rawPath =
|
||||||
|
purePath[purePath.length - 1] === '/' ? purePath.slice(0, -1) : purePath
|
||||||
|
const filename = rawPath === '' ? '/index.html' : `${rawPath}.html`
|
||||||
|
const fallback = getPath(rawPath + '/index.html')
|
||||||
|
const filePath = getPath(filename)
|
||||||
|
let html = await storage.getItem(filePath)
|
||||||
|
if (!html) html = await storage.getItem(fallback)
|
||||||
|
if (!html) html = await storage.getItem(getPath('/404.html'))
|
||||||
|
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
@ -4,3 +4,4 @@ export { getFiles } from './get-files.js'
|
||||||
export { hooks } from './hooks.mjs'
|
export { hooks } from './hooks.mjs'
|
||||||
export { injectCustomElements } from './inject-elements.js'
|
export { injectCustomElements } from './inject-elements.js'
|
||||||
export { injectHtmlFragments } from './inject-fragments.mjs'
|
export { injectHtmlFragments } from './inject-fragments.mjs'
|
||||||
|
export { mcflyNitroConfig as nitroConfig } from '../mcfly-nitro-config.js'
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function replaceSlots(fragmentNode, node) {
|
||||||
if (n.type === ELEMENT_NODE && n.name === 'slot') {
|
if (n.type === ELEMENT_NODE && n.name === 'slot') {
|
||||||
// find node child with same name attribute
|
// find node child with same name attribute
|
||||||
const currentSlotName = n.attributes?.['name'] ?? null
|
const currentSlotName = n.attributes?.['name'] ?? null
|
||||||
let nodeChildren
|
let nodeChildren = []
|
||||||
|
|
||||||
if (currentSlotName === null) {
|
if (currentSlotName === null) {
|
||||||
nodeChildren = node.children.filter(
|
nodeChildren = node.children.filter(
|
||||||
|
|
@ -78,11 +78,7 @@ function replaceSlots(fragmentNode, node) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (nodeChildren.length > 0 && !containsAll(slotted, nodeChildren)) {
|
||||||
nodeChildren &&
|
|
||||||
nodeChildren.length > 0 &&
|
|
||||||
!containsAll(slotted, nodeChildren)
|
|
||||||
) {
|
|
||||||
slotted = [...slotted, ...nodeChildren]
|
slotted = [...slotted, ...nodeChildren]
|
||||||
const index = n.parent.children.indexOf(n)
|
const index = n.parent.children.indexOf(n)
|
||||||
n.parent.children.splice(index, 1, ...nodeChildren)
|
n.parent.children.splice(index, 1, ...nodeChildren)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { ParsedArgs } from 'citty'
|
import type { ParsedArgs } from 'citty'
|
||||||
import consola from 'consola'
|
import consola from 'consola'
|
||||||
import { expect, it, vi } from 'vitest'
|
import { expect, it, vi } from 'vitest'
|
||||||
import { exportedForTest } from '../src/commands/build'
|
import { exportedForTest } from '../src/cli/commands/build.js'
|
||||||
|
|
||||||
const build = exportedForTest.build
|
const build = exportedForTest.build
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { consola } from 'consola'
|
import { consola } from 'consola'
|
||||||
import { it, expect, vi } from 'vitest'
|
import { it, expect, vi } from 'vitest'
|
||||||
import { exportedForTest } from '../src/commands/prepare'
|
import { exportedForTest } from '../src/cli/commands/prepare.js'
|
||||||
const prepare = exportedForTest.prepare
|
const prepare = exportedForTest.prepare
|
||||||
|
|
||||||
const mocks = vi.hoisted(() => {
|
const mocks = vi.hoisted(() => {
|
||||||
|
|
@ -9,6 +9,12 @@ const mocks = vi.hoisted(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
vi.mock('nitropack', () => {
|
||||||
|
return {
|
||||||
|
createNitro: mocks.createNitro,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('start prepare script', () => {
|
it('start prepare script', () => {
|
||||||
const spy = vi.spyOn(consola, 'start')
|
const spy = vi.spyOn(consola, 'start')
|
||||||
|
|
||||||
|
|
@ -17,6 +23,14 @@ it('start prepare script', () => {
|
||||||
expect(spy).toHaveBeenCalled()
|
expect(spy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it.skip('execute nitropack prepare', () => {
|
||||||
|
const successSpy = vi.spyOn(consola, 'success')
|
||||||
|
|
||||||
|
prepare({ dir: 'fakeDir', _: [] })
|
||||||
|
|
||||||
|
expect(successSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
it.skip('catch error', () => {
|
it.skip('catch error', () => {
|
||||||
const dir = 'fake-dir'
|
const dir = 'fake-dir'
|
||||||
const errSpy = vi.spyOn(consola, 'error')
|
const errSpy = vi.spyOn(consola, 'error')
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="250" src="https://git.ayo.run/ayo/mcfly/raw/branch/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
|
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">Create McFly</h1>
|
<h1 align="center">Create McFly</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
{
|
{
|
||||||
"name": "create-mcfly",
|
"name": "create-mcfly",
|
||||||
"version": "0.4.8",
|
"version": "0.4.7",
|
||||||
"description": "Create a new McFly app",
|
"description": "Create a new McFly app",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"create-mcfly": "./index.js"
|
"create-mcfly": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"main": "./index.js",
|
"main": "./dist/index.js",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"default": "./index.js"
|
"types": "./dist/index.d.ts",
|
||||||
|
"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.ayo.run/ayo/mcfly",
|
"url": "https://git.sr.ht/~ayoayco/mcfly",
|
||||||
"directory": "packages/create-mcfly"
|
"directory": "packages/create-mcfly"
|
||||||
},
|
},
|
||||||
"author": "Ayo Ayco",
|
"author": "Ayo Ayco",
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@ import { consola } from 'consola'
|
||||||
import { colorize } from 'consola/utils'
|
import { colorize } from 'consola/utils'
|
||||||
import { downloadTemplate } from 'giget'
|
import { downloadTemplate } from 'giget'
|
||||||
import { spawnSync } from 'node:child_process'
|
import { spawnSync } from 'node:child_process'
|
||||||
import path from 'node:path'
|
import * as path from 'node:path'
|
||||||
|
|
||||||
const [, , directoryArg] = process.argv
|
const [, , directoryArg] = process.argv
|
||||||
/**
|
|
||||||
* @typedef {Object} PromptAction
|
type PromptAction = {
|
||||||
* @property {string} prompt - The prompt text to display
|
prompt: string
|
||||||
* @property {string} [info] - Additional information about the prompt
|
info?: string
|
||||||
* @property {string} startMessage - Message to show when starting the action
|
startMessage: string
|
||||||
* @property {string} command - The command to execute
|
command: string
|
||||||
* @property {string} subCommand - The subcommand to execute
|
subCommand: string
|
||||||
* @property {string} error - Error message to display on failure
|
error: string
|
||||||
*/
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create McFly App
|
* Create McFly App
|
||||||
|
|
@ -42,10 +42,7 @@ 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',
|
||||||
|
|
@ -83,7 +80,7 @@ async function create() {
|
||||||
* @param {string} directory
|
* @param {string} directory
|
||||||
* @returns string
|
* @returns string
|
||||||
*/
|
*/
|
||||||
function getSafeDirectory(directory) {
|
function getSafeDirectory(directory: string): string {
|
||||||
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)
|
||||||
|
|
@ -95,14 +92,16 @@ function getSafeDirectory(directory) {
|
||||||
* @param {string} directory
|
* @param {string} directory
|
||||||
* @returns Promise<boolean> hasErrors
|
* @returns Promise<boolean> hasErrors
|
||||||
*/
|
*/
|
||||||
async function downloadTemplateToDirectory(directory) {
|
async function downloadTemplateToDirectory(
|
||||||
|
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('github:ayo-run/mcfly/templates/basic', {
|
await downloadTemplate('sourcehut:ayoayco/mcfly/templates/basic', {
|
||||||
dir: directory,
|
dir: directory,
|
||||||
})
|
})
|
||||||
} catch (ㆆ_ㆆ) {
|
} catch (ㆆ_ㆆ) {
|
||||||
|
|
@ -124,11 +123,11 @@ async function downloadTemplateToDirectory(directory) {
|
||||||
* @param {string} cwd
|
* @param {string} cwd
|
||||||
* @returns Promise<Array<boolean> | undefined>
|
* @returns Promise<Array<boolean> | undefined>
|
||||||
*/
|
*/
|
||||||
async function askPrompts(prompts, cwd) {
|
async function askPrompts(
|
||||||
/**
|
prompts: PromptAction[],
|
||||||
* @type {Array<boolean>}
|
cwd: string
|
||||||
*/
|
): Promise<boolean[] | undefined> {
|
||||||
const results = []
|
const results: boolean[] = []
|
||||||
|
|
||||||
for (const p of prompts) {
|
for (const p of prompts) {
|
||||||
const userIntends = await consola.prompt(p.prompt, {
|
const userIntends = await consola.prompt(p.prompt, {
|
||||||
|
|
@ -170,7 +169,7 @@ async function askPrompts(prompts, cwd) {
|
||||||
* @param {string} directory
|
* @param {string} directory
|
||||||
* @param {boolean} installDeps
|
* @param {boolean} installDeps
|
||||||
*/
|
*/
|
||||||
function showResults(directory, installDeps) {
|
function showResults(directory: string, installDeps: boolean) {
|
||||||
let nextActions = [
|
let nextActions = [
|
||||||
`Go to your project by running ${colorize('yellow', `cd ${directory}`)}`,
|
`Go to your project by running ${colorize('yellow', `cd ${directory}`)}`,
|
||||||
]
|
]
|
||||||
|
|
@ -190,7 +189,7 @@ function showResults(directory, installDeps) {
|
||||||
'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)
|
||||||
10
packages/create-mcfly/tsconfig.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"emitDeclarationOnly": false,
|
||||||
|
"declarationDir": "./dist",
|
||||||
|
"outDir": "./dist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
<p align="center">
|
|
||||||
<img width="250" src="https://git.ayo.run/ayo/mcfly/raw/branch/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)_
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
import serve from './serve'
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
return {
|
|
||||||
serve,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@mcflyjs/fastify",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"type": "module",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/autoload": "6.3.1",
|
|
||||||
"fastify": "5.8.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@mcflyjs/core": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
import Fastify from 'fastify'
|
|
||||||
import AutoLoad from '@fastify/autoload'
|
|
||||||
import path from 'node:path'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {import('@mcflyjs/core').ServerConfig} ServerConfig
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ServerConfig} param
|
|
||||||
*/
|
|
||||||
export default ({ rootDir, apiDir, logger, port }) => {
|
|
||||||
const server = Fastify()
|
|
||||||
const portNumber = port ?? 3000
|
|
||||||
|
|
||||||
server.register(AutoLoad, {
|
|
||||||
dir: path.join(rootDir, apiDir),
|
|
||||||
options: {
|
|
||||||
prefix: apiDir,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
server
|
|
||||||
.listen({ port: portNumber })
|
|
||||||
.then(() => {
|
|
||||||
logger.log(`API now serving at http://localhost:${portNumber}${apiDir}`)
|
|
||||||
})
|
|
||||||
.catch((err) => logger.error(err))
|
|
||||||
}
|
|
||||||
11721
pnpm-lock.yaml
|
|
@ -1,16 +1,4 @@
|
||||||
packages:
|
packages:
|
||||||
- 'packages/**'
|
- "packages/**"
|
||||||
- 'templates/**'
|
- "templates/**"
|
||||||
- 'site'
|
- "site/**"
|
||||||
- 'demo'
|
|
||||||
allowBuilds:
|
|
||||||
'@parcel/watcher': false
|
|
||||||
esbuild: true
|
|
||||||
netlify-cli: false
|
|
||||||
sharp: false
|
|
||||||
unix-dgram: false
|
|
||||||
web-component-base: true
|
|
||||||
minimumReleaseAgeExclude:
|
|
||||||
- '@mcflyjs/config@0.3.0-alpha'
|
|
||||||
- '@mcflyjs/core@0.9.0-alpha'
|
|
||||||
- '@mcflyjs/fastify@0.1.0'
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="250" src="https://git.ayo.run/ayo/mcfly/raw/branch/main/assets/mcfly-logo-sm.png" alt="McFly Logo" />
|
<img width="250" src="https://git.sr.ht/~ayoayco/mcfly/blob/main/assets/mcfly-logo-sm.png" alt="rRick & Morty cartoon" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h1 align="center">McFly Docs</h1>
|
<h1 align="center">McFly Docs</h1>
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
import { defineConfig } from '@mcflyjs/config'
|
import { defineMcFlyConfig } from '@mcflyjs/config'
|
||||||
// import testPlugin from './test-plugin.mjs'
|
// import testPlugin from './test-plugin.mjs'
|
||||||
|
// import content from '@mcflyjs/content'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineMcFlyConfig({
|
||||||
server: {},
|
|
||||||
components: 'js',
|
components: 'js',
|
||||||
// plugins: [testPlugin()],
|
// plugins: [testPlugin(), content()],
|
||||||
// nitro: {
|
nitro: {
|
||||||
// preset: 'netlify',
|
preset: 'netlify',
|
||||||
// devServer: {
|
devServer: {
|
||||||
// watch: ['../packages'],
|
watch: ['../packages'],
|
||||||
// },
|
},
|
||||||
// routeRules: {
|
routeRules: {
|
||||||
// '/chat': {
|
'/chat': {
|
||||||
// redirect: {
|
redirect: {
|
||||||
// to: 'https://matrix.to/#/#mcfly:matrix.org',
|
to: 'https://matrix.to/#/#mcfly:matrix.org',
|
||||||
// statusCode: 302,
|
statusCode: 302,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// compressPublicAssets: {
|
compressPublicAssets: {
|
||||||
// gzip: true,
|
gzip: true,
|
||||||
// brotli: true,
|
brotli: true,
|
||||||
// },
|
},
|
||||||
// compatibilityDate: '2024-12-08',
|
compatibilityDate: '2024-12-08',
|
||||||
// },
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://git.ayo.run/ayo/mcfly",
|
"url": "https://git.sr.ht/~ayoayco/mcfly",
|
||||||
"directory": "app"
|
"directory": "app"
|
||||||
},
|
},
|
||||||
"author": "Ayo Ayco",
|
"author": "Ayo Ayco",
|
||||||
|
|
|
||||||
|
|
@ -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.ayo.run/ayo/mcfly/tree/main/item/site/src/pages/demo.html"
|
href="https://git.sr.ht/~ayoayco/mcfly/tree/main/item/site/src/pages/demo.html"
|
||||||
>source code</a
|
>source code</a
|
||||||
>.</span></warning-block
|
>.</span></warning-block
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
// @ts-check
|
|
||||||
import { defineMcFlyConfig } from '@mcflyjs/config'
|
|
||||||
|
|
||||||
export default defineMcFlyConfig({
|
|
||||||
components: 'js',
|
|
||||||
})
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
<!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: "World",
|
|
||||||
count: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
updateLabel() {
|
|
||||||
this.props.myName = `Clicked ${++this.props.count}x`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get template() {
|
|
||||||
return html`
|
|
||||||
<button onClick=${() => this.updateLabel()} style="cursor:pointer">
|
|
||||||
Hello ${this.props.myName}!
|
|
||||||
</button>`;
|
|
||||||
}
|
|
||||||
}</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>
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineConfig } from '@mcflyjs/config'
|
// @ts-check
|
||||||
import fastify from '@mcflyjs/fastify'
|
import { defineMcFlyConfig } from '@mcflyjs/config'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineMcFlyConfig({
|
||||||
server: fastify(),
|
components: 'js',
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "mcfly-basic",
|
"name": "basic-template",
|
||||||
"version": "1.0.0",
|
"description": "McFly starter project",
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"start": "mcfly serve",
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mcflyjs/config": "^0.3.0-alpha",
|
"@mcflyjs/config": "^0.2.9",
|
||||||
"@mcflyjs/core": "^0.9.0-alpha",
|
"@mcflyjs/core": "^0.8.8"
|
||||||
"@mcflyjs/fastify": "^0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export default async (fastify) => {
|
|
||||||
fastify.get('/', async function (request, reply) {
|
|
||||||
console.log({ request, reply })
|
|
||||||
return 'This is an example'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export default async (fastify) => {
|
|
||||||
fastify.get('/', async function (request, reply) {
|
|
||||||
return 'This is the API Index'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
export default async function (fastify, opts) {
|
|
||||||
fastify.get('/', async function (request, reply) {
|
|
||||||
console.log({ opts, request, reply })
|
|
||||||
return { root: true }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,53 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<!--
|
||||||
<meta charset="UTF-8">
|
Hello! This page is an example McFly page.
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
See more on https://ayco.io/sh/mcfly
|
||||||
<title>WORK IN PROGRESS</title>
|
-->
|
||||||
</head>
|
<my-head>
|
||||||
<body>
|
<title>McFly: Back to the Basics. Into the Future.</title>
|
||||||
<h1>WORK IN PROGRESS</h1>
|
<script server:setup>
|
||||||
<p>See <a href="https://ayco.io/sh/mcfly">the project repository</a> for more info.</p>
|
const project = {
|
||||||
</body>
|
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: "World",
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLabel() {
|
||||||
|
this.props.myName = `Clicked ${++this.props.count}x`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get template() {
|
||||||
|
return html`
|
||||||
|
<button onClick=${() => this.updateLabel()} style="cursor:pointer">
|
||||||
|
Hello ${this.props.myName}!
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
}</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>
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |