When developing 'Prospekt' — the codename for the latest version of .rounds — I quickly recognized the need for a standardized way to create components, define routes, and facilitate seamless communication between the frontend and backend. This realization led to the creation of a full-stack framework called ".dot engine."
Initially, the framework was designed specifically to serve the .rounds project. However, it also provided an opportunity to deepen my understanding of web frameworks by building one from scratch rather than relying on existing solutions. This approach allowed me to explore the inner workings and underlying principles of such frameworks.
In its early stages, .dot development was reactive. Features were built on an as-needed basis to meet .rounds' requirements. By February 2023, the framework had achieved basic functionality.
During this phase, frontend rendering relied on sending a minimally structured HTML file, hoping browsers would autocorrect issues. Components were crafted with plain HTML and CSS. While functional, this approach was unreliable and led to browser inconsistencies.
At the time, .dot was tightly coupled with .rounds, including project-specific dependencies like a MySQL database driver. This integration made the framework less versatile and highlighted the need for a rewrite.
To maintain clarity, versions followed a consistent naming convention, reflecting the expected release month (e.g., 2.23 for February 2023). Versions were further classified into stages: dev, pre-release, and release. Minor updates were appended with incremental numbers for quick fixes.
The final bundled release of .dot with .rounds marked a turning point. As .rounds began to grow, a rewrite of the framework was needed, isolating the engine's functional parts from its specific use-case components.
The index file served as the entry point, redirecting all traffic to the engineInit
function:
<?php
# .DOT Engine
# Running dev 8.23
#
# Copyright by Rabbyte Software.
# All rights reserved 2021-2023
require_once '.dot/.php';
enengineInit();
exit;
The engineInit
function processed requests, either generating an HTML file or returning plain text:
function engineInit() {
require_once $_SERVER['DOCUMENT_ROOT'] . '/.dot/routing/routing.php'; // Render Engine
require_once $_SERVER['DOCUMENT_ROOT'] . '/.dot/routing/api.php'; // API Engine
require_once $_SERVER['DOCUMENT_ROOT'] . '/.dot/render/.php'; // Render Engine
$_SERVER['DOT_VERSION'] = "dev 10.23";
$_SERVER['DEFAULT_LANG'] = "en";
session_start();
$query = str_replace("/index.php?query=", "", $_SERVER['REQUEST_URI']);
$_SERVER['query'] = $query;
if (str_contains($query, "/API/")) {
return API(str_replace("/API", "", $query));
} else {
echo renderDraw::virtualDOM($query);
}
}
The main file allowed developers to inject custom code during rendering or routing:
class Main {
public static function Main($query) {
return true;
}
public static function onSceneRequest() {
return true;
}
public static function onApiRequest() {
return true;
}
public static function Head() {
?>
<link rel="icon" type="image/ico" href="/favicon.ico">
<title>.DOT Engine Basic Application</title>
<?php
return true;
}
public static function Content() {
return true;
}
public static function Credits() {
$year = date('Y');
return ".DOT Engine\nRunning {$_SERVER['DOT_VERSION']}\nCopyright by Rabbyte Software. All Rights Reserved 2021-{$year}.";
}
}
.dot supported extensions for additional functionality. For example, a WebSocket extension:
class websocket_mapper {
static function appendHead() {
if (!isset($_COOKIE['reconAuth'])) return;
ext\websocket\import($_COOKIE['reconAuth']);
}
static function credits() {
return "Websocket.recon (Client) - dev 6.23 Prospekt - with ❤️ by Char2cs";
}
}
Extensions could inject functions at various points in the engine by creating a mapper file inside the extension folder. A "dotfile" was also used for mounting functions into the engine for use anywhere in the app.
Creating an API endpoint was straightforward: either create a folder and use a "dotfile," or create a named file inside a folder.
namespace App\API;
use App\Ext\Settings;
if (!mail(Settings::get()["Contact"], $_POST["title"], $_POST["description"], "From: {$_POST['email']}\n")) {
throw new \Exception("Email could not be sent!");
}
Routes or "scenes" were defined in ".dot files." For example, a hello-world route:
namespace App\Scenes\Index;
?>
<section><h1>Hello world</h1></section>
<?php
Dynamic routes used a __
prefix, similar to frameworks like Next.js.
Environment variables were managed using YAML files, although this behavior could change, as this functionality was served by a default extension.
MySQL:
hostname: ...
username: ...
password: ...
database: ...
Modules dynamically generated JavaScript or CSS tags. Later, a virtual file system compiled these modules at runtime for improved performance.
MODULES::link("/Scenes/ui/.css"); # CSS Files
MODULES::src("/Scenes/ui/.js"); # Javascript
\MODULES::script(<<<JS
window.debug = true;
document.addEventListener("DOMContentLoaded", () => {
const imsButton = document.querySelector(`.admin-header > .btns > a[data-ims="{$ims}"]`);
imsButton.classList.add("active");
});
JS);
i18n support was simple and easy to implement. On a new Scene, you could also create a lang
folder containing files with translations for each language. Each language had a single file written in yaml
:
ADMIN_HOME : "Inicio"
ADMIN_SUBMIT : "Guardar"
ADMIN_REMOVE : "Eliminar"
ADMIN_ADD : "Añadir"
ADMIN_DESCRIPTION : "Descripción"
ADMIN_UPLOAD : "Subir"
ADMIN_HOME : "Home"
ADMIN_SUBMIT : "Save"
ADMIN_REMOVE : "Remove"
ADMIN_ADD : "Add"
ADMIN_DESCRIPTION : "Description"
ADMIN_UPLOAD : "Upload"
On the actual HTML template, you just had to reference each message name between %
:
<li onmouseleave="window.ItemMouseLeave(this)" data-item="<?=$_GET["ims"]?>/add">
<a class="title" onmouseenter="window.ItemMouseEnter(this)" href="/<?=$_GET["ims"]?>/add">
<span class="material-symbols-outlined">add</span>
%ADMIN_ADD%
</a>
</li>
.dot included a Software Development Kit (SDK) for configuring services such as MySQL, Redis, and Node.js. The SDK was adapted from a LAMP Dockerized SDK available on GitHub. This was also my first attempt at dockerizing apps and service stacks.
Since .dot relied heavily on web server configurations, it provided optimized configuration files for NGINX and Apache servers, ensuring smooth deployment.
As it stands, the .dot engine is on standby, and there are no plans to develop this engine further. Support remains active, as a few applications in production still rely on it.