Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,42 @@ framework are exposed.

**Please** read the user guide for a better explanation of how CI4 works!

# 🔥 Hot Reload for Views (Development Feature)

CodeIgniter 4 includes a lightweight **Hot Reload** system designed to improve development workflow.
It automatically refreshes the browser whenever a file inside `app/Views/` changes — with **no external tools required**.

### ✨ Key Features

- No **Node**, **Webpack**, **Vite**, or external watchers
- Runs **entirely in PHP**
- Starts **automatically** when you run:

`php spark serve`

When the server starts, CodeIgniter launches:

1. **The PHP development server**
2. **A file watcher** that monitors:

app/Views/

Whenever a view file changes, the watcher updates a timestamp.
Your browser polls `/hotreload/check`, and when it detects an update, the page reloads instantly.

---

## ⚡ Enabling Hot Reload in Your Views

To activate Hot Reload on any view, include the following partial:

```php
<?= $this->include('partials/hotreload') ?>
```
You may place this partial:

✔ In each view

## Repository Management

CodeIgniter is developed completely on a volunteer basis. As such, please give up to 7 days
Expand Down
56 changes: 56 additions & 0 deletions app/Commands/HotReload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace App\Commands;

use CodeIgniter\CLI\BaseCommand;

class HotReload extends BaseCommand
{
protected $group = 'Development';
protected $name = 'hotreload';
protected $description = 'Monitor changes in app/Views and trigger browser reload.';

public function run(array $params)
{
$watchPath = APPPATH . 'Views';
$flagFile = WRITEPATH . 'cache/hotreload.txt';

if (!file_exists($flagFile)) {
file_put_contents($flagFile, time());
}

echo "🚀 HotReload activo — Monitoreando: {$watchPath}\n";

$lastHash = null;

while (true) {
clearstatcache();

$currentHash = md5(json_encode($this->scanFiles($watchPath)));

if ($lastHash !== null && $currentHash !== $lastHash) {
file_put_contents($flagFile, time());
echo "♻ Cambio detectado → Recargando navegador…\n";
}

$lastHash = $currentHash;
sleep(1);
}
}

private function scanFiles($path)
{
$rii = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
$files = [];

foreach ($rii as $file) {
if ($file->isDir()) continue;
$files[] = [
'file' => $file->getPathname(),
'mtime' => $file->getMTime(),
];
}

return $files;
}
}
36 changes: 36 additions & 0 deletions app/Commands/Serve.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\Commands;

use CodeIgniter\CLI\CLI;
use CodeIgniter\Commands\Server\Serve as CoreServe;

class Serve extends CoreServe
{
protected $group = 'CodeIgniter';
protected $name = 'serve';
protected $description = 'Start CI4 dev server with integrated HotReload.';

public function run(array $params)
{
CLI::write("🚀 Iniciando servidor con HotReload integrado…", 'green');

$this->startHotReloadWatcher();
return parent::run($params);
}

private function startHotReloadWatcher()
{
$php = PHP_BINARY;
$spark = realpath(ROOTPATH . 'spark');

if (stripos(PHP_OS, 'WIN') === 0) {
$cmd = "start /B $php $spark hotreload >nul 2>&1";
pclose(popen($cmd, "r"));
} else {
shell_exec("$php $spark hotreload > /dev/null 2>&1 &");
}

CLI::write("🔁 HotReload watcher iniciado.", 'yellow');
}
}
5 changes: 5 additions & 0 deletions app/Config/Routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@
* @var RouteCollection $routes
*/
$routes->get('/', 'Home::index');

/**
*HOT RELOAD ROUTE
*/
$routes->get('hotreload/check', 'HotReloadController::check');
21 changes: 21 additions & 0 deletions app/Controllers/HotReloadController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace App\Controllers;

use CodeIgniter\Controller;

class HotReloadController extends Controller
{
public function check()
{
$cacheFile = WRITEPATH . 'cache/hotreload.txt';

if (!file_exists($cacheFile)) {
file_put_contents($cacheFile, '0');
}

return $this->response->setJSON([
'hash' => file_get_contents($cacheFile)
]);
}
}
21 changes: 21 additions & 0 deletions app/Views/partials/hotreload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script>
let lastHash = null;

async function checkReload() {
try {
const res = await fetch("<?= base_url('/hotreload/check') ?>");
const data = await res.json();

if (lastHash === null) {
lastHash = data.hash;
} else if (data.hash !== lastHash) {
console.log("HotReload: cambio detectado, recargando...");
location.reload();
}
} catch (e) {
console.warn("HotReload error:", e);
}
}

setInterval(checkReload, 1000);
</script>
3 changes: 2 additions & 1 deletion app/Views/welcome_message.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<meta name="description" content="The small framework with powerful features">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" type="image/png" href="/favicon.ico">

<!-- HOT RELOAD -->
<?= $this->include('partials/hotreload') ?>
<!-- STYLES -->

<style {csp-style-nonce}>
Expand Down