.

d

o

t

 

e

n

g

i

n

e

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

Purpose and Goals

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.

Early Development

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.

Versioning System

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.

Transition to Independence

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.

Engine Structure

Index File

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);
    }
}

Main File

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}.";
    }
}

Extensions

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

API Integration

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!");
}

Routing

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

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

Modules dynamically generated JavaScript or CSS tags. Later, a virtual file system compiled these modules at runtime for improved performance.

Linkage to a File

MODULES::link("/Scenes/ui/.css"); # CSS Files
MODULES::src("/Scenes/ui/.js"); # Javascript

Inline Scripting/Styling

\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

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:

es.yaml

ADMIN_HOME   : "Inicio"
ADMIN_SUBMIT : "Guardar"
ADMIN_REMOVE : "Eliminar"
ADMIN_ADD    : "Añadir"
ADMIN_DESCRIPTION : "Descripción"
ADMIN_UPLOAD : "Subir"

en.yaml

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>

SDK

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

Production Deployment

Since .dot relied heavily on web server configurations, it provided optimized configuration files for NGINX and Apache servers, ensuring smooth deployment.

Project Future

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.