Skip to content

Commit

Permalink
feat: improve livereload
Browse files Browse the repository at this point in the history
  • Loading branch information
BernhardBaumrock committed Mar 11, 2024
1 parent 7db7970 commit fa67da2
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 113 deletions.
1 change: 0 additions & 1 deletion LiveReload.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ public function watch()

// sleep until next try
$sleepSeconds = (float)$this->wire->config->livereload ?: 1.0;
$sleepSeconds = 0.2;
usleep($sleepSeconds * 1000000);
}
}
Expand Down
176 changes: 79 additions & 97 deletions RockFrontend.module.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,16 @@ class RockFrontend extends WireData implements Module, ConfigurableModule
public function __construct()
{
$this->folders = $this->wire(new WireArray());
if (!$this->wire->config->livereload) return;
if ($this->wire->config->ajax) return;

// is this a livereload request having the proper get param in the url?
if (!array_key_exists(self::getParam, $_GET)) return;

// if livereload is not enabled we show a message for better debugging
if (!$this->wire->config->livereload) {
die("LiveReload is not enabled in the config!");
}

// attach hook to stream file changes to the browser
$this->addHookBefore("Session::init", function (HookEvent $event) {

// disable tracy for the SSE stream
Expand Down Expand Up @@ -190,6 +197,7 @@ public function init()
// set the remBase either from config setting or use 16 as fallback
$this->remBase = $this->remBase ?: 16;
$this->initPostCSS();
$this->js('isDDEV', array_key_exists('DDEV_VERSION', $_ENV));

// watch this file and run "migrate" on change or refresh
if ($rm = $this->rm()) $rm->watch($this, 0.01);
Expand All @@ -213,15 +221,17 @@ public function init()
$this->lessToCss($this->path . "Alfred.less");

// hooks
$this->addHookAfter("ProcessPageEdit::buildForm", $this, "hideLayoutField");
$this->addHook(self::tagsUrl, $this, "layoutSuggestions");
$this->addHookAfter("Modules::refresh", $this, "refreshModules");
$this->addHookBefore('TemplateFile::render', $this, "autoPrepend");
$this->addHookAfter("InputfieldForm::processInput", $this, "createWebfontsFile");
$this->addHookBefore("Inputfield::render", $this, "addFooterlinksNote");
$this->addHookAfter("Page::changed", $this, "resetCustomLess");
$this->addHookBefore("Page::render", $this, "createCustomLess");
$this->addHookMethod("Page::otherLangUrl", $this, "otherLangUrl");
wire()->addHookAfter("ProcessPageEdit::buildForm", $this, "hideLayoutField");
wire()->addHook(self::tagsUrl, $this, "layoutSuggestions");
wire()->addHookAfter("Modules::refresh", $this, "refreshModules");
wire()->addHookBefore("TemplateFile::render", $this, "autoPrepend");
wire()->addHookAfter("InputfieldForm::processInput", $this, "createWebfontsFile");
wire()->addHookBefore("Inputfield::render", $this, "addFooterlinksNote");
wire()->addHookAfter("Page::changed", $this, "resetCustomLess");
wire()->addHookBefore("Page::render", $this, "createCustomLess");
wire()->addHookMethod("Page::otherLangUrl", $this, "otherLangUrl");
wire()->addHookAfter("Modules::refresh", $this, "livereloadResetCache");
wire()->addHookAfter("Page::render", $this, "livereloadAddMarkup");

// others
$this->ajaxAddEndpoints();
Expand All @@ -236,7 +246,6 @@ public function init()

public function ready()
{
$this->liveReload();
$this->addAssets();
$this->copyLayoutFileIfNewer();
}
Expand All @@ -258,7 +267,6 @@ function (HookEvent $event) {
if (!strpos($html, "</body>")) return;
if (!strpos($html, "</head>")) return;

$this->addLiveReloadSecret();
$this->addRockFrontendJS();
$this->addAlfredMarkup($html);
$this->addTopBar($html);
Expand Down Expand Up @@ -298,22 +306,6 @@ private function addAlfredMarkup(string &$html): void
$html = str_replace("</body", "$faketag</body", $html);
}

private function addLiveReloadSecret(): void
{
if (!$this->wire->config->livereload) return;
$this->js("rootUrl", $this->wire->config->urls->root);

// create secret and send it to js
/** @var WireRandom $rand */
$rand = $this->wire(new WireRandom());
$cache = $this->wire->cache->get(self::livereloadCacheName);
if (!is_array($cache)) $cache = [];
$secret = $rand->alphanumeric(0, ['minLength' => 30, 'maxLength' => 40]);
$merged = array_merge($cache, [$secret]);
$this->wire->cache->save(self::livereloadCacheName, $merged);
$this->js("livereloadSecret", $secret);
}

private function addRockFrontendJS(): void
{
if (!$this->isEnabled('RockFrontend.js')) return;
Expand Down Expand Up @@ -344,35 +336,6 @@ public function addFooterlinksNote(HookEvent $event)
$f->notes .= "Superuser Note: Use \$rockfrontend->footerlinks() to access links as a PageArray in your template file (ready for foreach).";
}

private function addLiveReloadScript()
{
// get and minify the livereload script
// dont worry, this will only be done for superusers ;)
$file = $this->minifyFile($this->path . "livereload.js");
$page = $this->wire->page;

// for backend requests we need more caution
// this is because live reload will break the module installation screen for example
if ($page->template == 'admin') {
// if livereload is disabled on backend pages we exit early
if (!$this->livereloadBackend) return;

// on module config screens we disable livereload if it is not explicitly
// forced to be enabled. this is to prevent problems when downloading
// and installing modules!
if ($page->process == "ProcessModule" && !$this->liveReloadModules) return;
}

// if we got that far we add livereload to our site :)
$this->scripts('rockfrontend')->add($file, "defer");
}

public function getLiveReloadFile($returnUrl = false): string
{
$file = $this->minifyFile($this->path . "livereload.js");
if ($returnUrl) return $this->url($this->wire->config->versionUrl($file));
}

/**
* Return link to add a new page under given parent
* @return string
Expand Down Expand Up @@ -484,14 +447,9 @@ protected function ajaxAddEndpoints(): void

private function ajaxDebug($endpoint): string
{
// get response
try {
$raw = $this->ajaxResponse($endpoint);
$response = Dumper::toHtml($raw);
} catch (\Throwable $th) {
$this->log($th->getMessage());
$response = var_dump($raw);
}
// dont catch errors when debugging
$raw = $this->ajaxResponse($endpoint);
$response = Dumper::toHtml($raw);

// render html
$markup = $this->render(__DIR__ . "/stubs/ajax-debug.latte", [
Expand All @@ -500,9 +458,6 @@ private function ajaxDebug($endpoint): string
'formatted' => $this->ajaxFormatted($raw, $endpoint),
]);

// add live reload and return markup
$this->addLiveReloadSecret();
$this->injectJavascriptSettings($markup);
return $markup;
}

Expand Down Expand Up @@ -548,12 +503,15 @@ private function ajaxPublic($endpoint): string
return $response;
} catch (\Throwable $th) {
$this->log($th->getMessage());
return "Error in AJAX endpoint";
return "Error in AJAX endpoint - error has been logged";
}
}

private function ajaxResponse($endpoint)
{
if ($this->wire->user->isSuperuser()) {
return $this->wire->files->render($endpoint);
}
try {
return $this->wire->files->render($endpoint);
} catch (\Throwable $th) {
Expand Down Expand Up @@ -1543,43 +1501,67 @@ public function lighten($hex, $percent): string
return $this->adjustBrightness($hex, $percent / 100);
}

/**
* Setup live reloading
*/
public function livereload()
protected function livereloadAddMarkup(HookEvent $event)
{
// early exit if live reload is disabled
if (!$this->wire->config->livereload) return;

if ($this->wire->page->template == 'admin') {
$file = $this->wire->config->paths->root . "livereload.php";
if ($this->wire->user->isSuperuser() and is_file($file)) {
$this->warning("Found file $file which is not used any more - you can delete it");
}
}
$config = $this->wire->config;
if ($config->ajax) return;
if ($config->external) return;
if ($this->livereloadAdded) return;

// early exit when page is opened in modal window
// this is to prevent enless reloads when the parent frame is reloading
if ($this->wire->input->get('modal')) return;
// reset the livereload secret on every modules refresh
$cachefile = $this->wire->config->paths->cache . self::livereloadCacheName . ".txt";
$this->addHookAfter("Modules::refresh", function () use ($cachefile) {
if (is_file($cachefile)) $this->wire->files->unlink($cachefile);
$this->wire->cache->save(self::livereloadCacheName, null);
});

// add script that triggers stream on frontend
$this->addLiveReloadScript();
$page = $this->wire->page;

// for backend requests we need more caution
// this is because live reload will break the module installation screen for example
if ($page->template == 'admin') {
// if livereload is disabled on backend pages we exit early
$livereloadBackend = $this->livereloadBackend ?: $config->livereloadBackend;
if (!$livereloadBackend) return;

// on module config screens we disable livereload if it is not explicitly
// forced to be enabled. this is to prevent problems when downloading
// and installing modules!
if ($page->process == "ProcessModule" && !$this->liveReloadModules) return;
}

$this->livereloadAdded = true;
$event->return .= $this->liveReloadMarkup();
}

/**
* Get the script tag that is necessary for livereload to be present
*/
public function livereloadScriptTag(): string
public function liveReloadMarkup(): string
{
// create secret and send it to js
/** @var WireRandom $rand */
$rand = $this->wire(new WireRandom());
$cache = $this->wire->cache->get(self::livereloadCacheName);
if (!is_array($cache)) $cache = [];
$secret = $rand->alphanumeric(0, ['minLength' => 30, 'maxLength' => 40]);
$merged = array_merge($cache, [$secret]);
$this->wire->cache->save(self::livereloadCacheName, $merged);

// get and minify the livereload script
// dont worry, this will only be done for superusers ;)
$file = $this->minifyFile($this->path . "livereload.js");
$url = $this->url($file, true);
return "<script>var RockFrontend;</script><script src=$url defer></script>";
$src = $this->url($file, true);

return "
<script>
var LiveReloadUrl = '{$this->wire->config->urls->root}';
var LiveReloadSecret = '$secret';
console.log('Loading LiveReload');
</script>
<script src='$src'></script>
";
}

protected function livereloadResetCache(): void
{
$cachefile = $this->wire->config->paths->cache . self::livereloadCacheName . ".txt";
if (is_file($cachefile)) $this->wire->files->unlink($cachefile);
$this->wire->cache->save(self::livereloadCacheName, null);
}

/**
Expand Down
15 changes: 1 addition & 14 deletions livereload.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ setTimeout(() => {

let evtSource;
let startStream = function () {
let rf = RockFrontend;
let url = rf.rootUrl + "?rockfrontend-livereload=" + rf.livereloadSecret;
let url = LiveReloadUrl + "?rockfrontend-livereload=" + LiveReloadSecret;
evtSource = new EventSource(url, { withCredentials: true });
evtSource.onmessage = function (event) {
let changed = event.data;
Expand Down Expand Up @@ -72,16 +71,4 @@ setTimeout(() => {

startStream();
console.log("RockFrontend is listening for file changes...");

// this causes livereload to break if vscode links are clicked
// TODO: find a better solution to fix the firefox issue
// // prevent error msg in Firefox https://bugzilla.mozilla.org/show_bug.cgi?id=833462
// window.addEventListener("beforeunload", () => {
// evtSource.close();
// setTimeout(() => {
// // we restart the stream because clicks on vscode links would
// // otherwise stop the livereload watcher stream!
// startStream();
// }, 50);
// });
}, 1000);
2 changes: 1 addition & 1 deletion livereload.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit fa67da2

Please sign in to comment.