Skip to content

Commit

Permalink
Merge pull request #2 from bangnokia/feat/hot-reloading
Browse files Browse the repository at this point in the history
LGTM
  • Loading branch information
bangnokia authored Apr 7, 2024
2 parents e493214 + 44b48d9 commit 8187d0d
Show file tree
Hide file tree
Showing 11 changed files with 337 additions and 76 deletions.
82 changes: 82 additions & 0 deletions app/Commands/HttpServeCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

namespace BangNokia\Lina\Commands;

use LaravelZero\Framework\Commands\Command;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

class HttpServeCommand extends Command
{
protected $signature = 'serve:http';

protected $description = 'Start simple web server for development';

protected $hidden = true;

protected int $portOffset = 0;

public function handle()
{
$this->call('clean');

$this->line("<info>Starting development server:</info> http://{$this->host()}:{$this->port()}");

$process = $this->startProcess();

while ($process->isRunning()) {
usleep(0.5 * 1000000);
}

$status = $process->getExitCode();

if ($status && $this->portOffset++ < 10) {
$this->handle();
}

return $status;
}

protected function startProcess()
{
$process = new Process($this->serverCommand(), timeout: 0);

$process->start(function ($type, $data) {
// $this->output->write($data);
});

// Stop the server when the user hits Ctrl+C
// to void the port in used error
$this->trap(fn () => [SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT], function ($signal) use ($process) {
if ($process->isRunning()) {
$process->stop(10, $signal);
}

exit;
});

return $process;
}

protected function serverCommand()
{
return [
(new PhpExecutableFinder)->find(false),
'-S',
$this->host().':'.$this->port(),
'-t',
'public',
base_path('server.php')
];
}

protected function host()
{
return '127.0.0.1';
}

protected function port()
{
return 6969 + $this->portOffset;
}
}
75 changes: 14 additions & 61 deletions app/Commands/ServeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,32 @@

namespace BangNokia\Lina\Commands;

use Illuminate\Process\Pool;
use Illuminate\Support\Facades\Process;
use LaravelZero\Framework\Commands\Command;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;

class ServeCommand extends Command
{
protected $signature = 'serve';

protected $description = 'Start simple web server for development';
protected $description = 'Start development server';

protected int $portOffset = 0;

public function handle()
public function handle(): int
{
$this->call('clean');

$this->line("<info>Starting development server:</info> http://{$this->host()}:{$this->port()}");

$process = $this->startProcess();
$phpBinary = (new PhpExecutableFinder())->find();
// get the current php binary path which is running the command
$pool = Process::pool(function (Pool $pool) use ($phpBinary) {
$pool->path(getcwd())->command([$phpBinary, base_path('lina'), 'serve:http']);
$pool->path(getcwd())->command([$phpBinary, base_path('lina'), 'serve:ws']);
})->start(function (string $type, string $output, string $key) {
$this->output->write($output);
});

while ($process->isRunning()) {
while ($pool->running()->isNotEmpty()) {
usleep(0.5 * 1000000);
}

$status = $process->getExitCode();

if ($status && $this->portOffset++ < 10) {
$this->handle();
}

return $status;
}

protected function startProcess()
{
$process = new Process($this->serverCommand(), timeout: 0);

$process->start(function ($type, $data) {
$this->output->write($data);
});

// Stop the server when the user hits Ctrl+C
// to void the port in used error
$this->trap(fn () => [SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT], function ($signal) use ($process) {
if ($process->isRunning()) {
$process->stop(10, $signal);
}

exit;
});

return $process;
}

protected function serverCommand()
{
return [
(new PhpExecutableFinder)->find(false),
'-S',
$this->host().':'.$this->port(),
'-t',
'public',
base_path('server.php')
];
}

protected function host()
{
return '127.0.0.1';
}

protected function port()
{
return 6969 + $this->portOffset;
$pool->wait();
}
}
127 changes: 127 additions & 0 deletions app/Commands/WebsocketServeCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace BangNokia\Lina\Commands;

use BangNokia\Lina\Socket;
use BangNokia\Lina\Watcher;
use Illuminate\Support\Facades\Cache;
use LaravelZero\Framework\Commands\Command;
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use React\EventLoop\Loop;
use React\EventLoop\LoopInterface;
use Symfony\Component\Finder\Finder;
use Ratchet\ConnectionInterface;
use React\Socket\SocketServer as Reactor;


class WebsocketServeCommand extends Command
{
protected $signature = 'serve:ws';

protected $description = 'Start websocket server for development';

protected $hidden = true;

protected LoopInterface $loop;

protected IoServer $server;

public static int $port = 9696;

public static int $portOffset = 0;

public function handle()
{
$this->loop = Loop::get();

$this->loop->futureTick(function () {
$this->line("<info>Starting websocket server:</info> ws://{$this->host()}:{$this->port()}");
});

$this
->startWatcher()
->startServer();
}

protected function startWatcher(): static
{
$dirs = $this->dirs();

if (empty($dirs)) {
$this->warn('No directory to watch, please check you are in the correct directory.');
return $this;
}

$finder = (new Finder())->files()->in($this->dirs());

(new Watcher($this->loop, $finder))
->startWatching(function () {
$this->info('Changes detected, reloading...');
collect(Socket::$clients)
->map(function (ConnectionInterface $client) {
$client->send('reload');
});
});

return $this;
}

protected function startServer(): static
{
try {
$this->server = new IoServer(
new HttpServer(new WsServer(new Socket())),
new Reactor("{$this->host()}:{$this->port()}", [], $this->loop),
$this->loop
);
$this->loop->addPeriodicTimer(1, fn() => Cache::put('ws_is_running', true, 5));

$this->server->run();
} catch (\Exception $exception) {
if (static::$portOffset < 10) {
static::$portOffset++;
$this->startServer();
}
}

return $this;
}

public static function isRunning(): bool
{
return Cache::get('ws_is_running', false);
}

public static function host()
{
return '127.0.0.1';
}

public static function port()
{
return static::$port + static::$portOffset;
}

protected function dirs(): array
{
$currentDir = getcwd();

$proposalDirs = [
$currentDir . '/content',
$currentDir . '/public',
$currentDir . '/resources/views',
];

$realDirs = [];

foreach ($proposalDirs as $dir) {
if (is_dir($dir)) {
$realDirs[] = $dir;
}
}

return $realDirs;
}
}
5 changes: 1 addition & 4 deletions app/MarkdownParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace BangNokia\Lina;

use BangNokia\Lina\Contracts\MarkdownParser as MarkdownParserContract;
use ParsedownToC;

class MarkdownParser implements MarkdownParserContract
{
Expand All @@ -16,8 +15,6 @@ public function __construct()

public function parse(string $text): string
{
$content = trim($this->driver->text($text));
// dd($content);
return $content;
return trim($this->driver->text($text));
}
}
5 changes: 2 additions & 3 deletions app/MarkdownRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace BangNokia\Lina;

use BangNokia\Lina\Contracts\Renderer;
use Illuminate\Support\Facades\Blade;

class MarkdownRenderer implements Renderer
{
Expand All @@ -17,9 +16,9 @@ public function __construct(protected string $rootDir)
config(['view.compiled' => $this->rootDir . '/resources/cache']);
}

public function render(string $realPath): string
public function render(string $file): string
{
$content = app(ContentFinder::class)->get($realPath, true);
$content = app(ContentFinder::class)->get($file, true);

return view($content->layout, [
'data' => $content,
Expand Down
28 changes: 27 additions & 1 deletion app/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace BangNokia\Lina;

use BangNokia\Lina\Commands\WebsocketServeCommand;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -29,13 +31,37 @@ public function parse(Request $request): Response

$contentFileRealPath = $this->contentFinder->tryFind($path);

$html = app(MarkdownRenderer::class)->render($contentFileRealPath);

// we don't have middleware so let inject the websocket script here
$html = $this->injectWebSocketScript($html);

return new Response(
app(MarkdownRenderer::class)->render($contentFileRealPath),
$html,
200,
['Content-Type' => 'text/html']
);
}

protected function injectWebSocketScript(string $html): string
{
$port = WebsocketServeCommand::port();

$script = <<<JS
<script>
(new WebSocket('ws://127.0.0.1:$port')).onmessage = function (message) {
if (message.data === 'reload') {
window.location.reload(true);
}
};
</script>
JS;

$html = $html . $script; // so who care about well-formed html here xD!

return $html;
}

protected function isStaticFile(string $path): bool
{
return in_array(pathinfo($path, PATHINFO_EXTENSION), ['css', 'js', 'png', 'jpg', 'jpeg', 'gif', 'svg']);
Expand Down
Loading

0 comments on commit 8187d0d

Please sign in to comment.