diff --git a/app/resources/views/base-view.blade.php b/app/resources/views/base-view.blade.php
new file mode 100644
index 0000000..5d141bc
--- /dev/null
+++ b/app/resources/views/base-view.blade.php
@@ -0,0 +1,3 @@
+
+ @include('included-view')
+
\ No newline at end of file
diff --git a/app/resources/views/included-view.blade.php b/app/resources/views/included-view.blade.php
new file mode 100644
index 0000000..7709ae4
--- /dev/null
+++ b/app/resources/views/included-view.blade.php
@@ -0,0 +1,10 @@
+
+
+
+
+
The message is:
+
+
+
\ No newline at end of file
diff --git a/app/resources/views/regular-view.blade.php b/app/resources/views/regular-view.blade.php
new file mode 100644
index 0000000..aa0883f
--- /dev/null
+++ b/app/resources/views/regular-view.blade.php
@@ -0,0 +1,10 @@
+
+
+
+
+
The message is:
+
+
+
\ No newline at end of file
diff --git a/app/routes/web.php b/app/routes/web.php
index 7d93d90..1262007 100644
--- a/app/routes/web.php
+++ b/app/routes/web.php
@@ -21,6 +21,8 @@
return view('welcome');
});
+Route::view('/regular-view', 'regular-view');
+Route::view('/base-view', 'base-view');
Route::view('/anonymous', 'anonymous');
Route::view('/blade-method-callbacks', 'blade-method-callbacks');
Route::view('/blade-method', 'blade-method');
diff --git a/src/ComponentHelper.php b/src/ComponentHelper.php
index 9272747..a9c78a8 100644
--- a/src/ComponentHelper.php
+++ b/src/ComponentHelper.php
@@ -76,10 +76,12 @@ public function getTag(Component|View|string $component): ?string
$class = $this->getClass($component);
if (! $class) {
+ $isComponent = $component instanceof Component || Str::contains($component, 'components.');
+
return Str::of($this->getAlias($component))
->replace('.', ' ')
->studly()
- ->prepend('SpladeComponent')
+ ->prepend($isComponent ? 'SpladeComponent' : 'Splade')
->toString();
}
diff --git a/src/ExtractVueScriptFromBladeView.php b/src/ExtractVueScriptFromBladeView.php
index 6b4cfc4..ef06eff 100644
--- a/src/ExtractVueScriptFromBladeView.php
+++ b/src/ExtractVueScriptFromBladeView.php
@@ -3,14 +3,19 @@
namespace ProtoneMedia\SpladeCore;
use Illuminate\Filesystem\Filesystem;
+use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Process;
+use Illuminate\Support\Js;
use Illuminate\Support\Str;
class ExtractVueScriptFromBladeView
{
protected readonly string $originalScript;
+ protected array $viewRootLayoutTags = [];
+
protected string $viewWithoutScriptTag;
protected ScriptParser $scriptParser;
@@ -30,12 +35,57 @@ public static function from(string $originalView, array $data, string $bladePath
return new static($originalView, $data, $bladePath);
}
+ /**
+ * Check if the view is a Blade Component.
+ */
+ protected function isComponent(): bool
+ {
+ return array_key_exists('spladeBridge', $this->data)
+ && Str::contains($this->bladePath, '/components/');
+ }
+
+ /**
+ * Check if the view has a ", '')
->trim()
->toString();
+
+ $this->extractWrappedViewInRootLayout();
+
+ if (empty($this->viewRootLayoutTags)) {
+ return;
+ }
+
+ $this->viewWithoutScriptTag = Str::of($this->viewWithoutScriptTag)
+ ->after($this->viewRootLayoutTags[0])
+ ->beforeLast($this->viewRootLayoutTags[1])
+ ->trim()
+ ->toString();
}
/**
@@ -170,6 +263,10 @@ protected function splitOriginalView(): void
*/
protected function replaceComponentMethodLoadingStates(string $script): string
{
+ if (! $this->isComponent()) {
+ return $script;
+ }
+
$methods = ['refreshComponent', ...$this->getBladeFunctions()];
return preg_replace_callback('/(\w+)\.loading/', function ($matches) use ($methods) {
@@ -196,10 +293,9 @@ protected function replaceElementRefs(string $script): string
*/
protected function extractDefinePropsFromScript(): array
{
- $defaultProps = Collection::make([
- 'spladeBridge' => 'Object',
- 'spladeTemplateId' => 'String',
- ])->when($this->viewUsesVModel(), fn (Collection $collection) => $collection->put('modelValue', '{}'));
+ $defaultProps = Collection::make(['spladeTemplateId' => 'String'])
+ ->when($this->isComponent(), fn (Collection $collection) => $collection->put('spladeBridge', 'Object'))
+ ->when($this->viewUsesVModel(), fn (Collection $collection) => $collection->put('modelValue', '{}'));
$defineProps = $this->scriptParser->getDefineProps($defaultProps->all());
@@ -222,7 +318,7 @@ protected function renderImports(): string
->push('h')
->when($this->needsSpladeBridge(), fn ($collection) => $collection->push('ref'))
->when($this->isRefreshable(), fn ($collection) => $collection->push('inject'))
- ->unless(empty($this->getBladeProperties()), fn ($collection) => $collection->push('computed'))
+ ->when($this->isComponent() && ! empty($this->getBladeProperties()), fn ($collection) => $collection->push('computed'))
->unique()
->sort()
->implode(',');
@@ -285,6 +381,10 @@ protected static function renderBladePropertyAsComputedVueProperty(string $prope
*/
protected function renderBladePropertiesAsComputedVueProperties(): string
{
+ if (! $this->isRefreshable()) {
+ return '';
+ }
+
$lines = [];
foreach ($this->getBladeProperties() as $property) {
diff --git a/src/PendingView.php b/src/PendingView.php
new file mode 100644
index 0000000..b39c530
--- /dev/null
+++ b/src/PendingView.php
@@ -0,0 +1,51 @@
+originalView = $originalView;
+
+ return $this;
+ }
+
+ public function render(string $hash): string
+ {
+ $bridge = Js::from(['tag' => $this->tag, 'template_hash' => $hash])->toHtml();
+
+ if (empty($this->rootLayoutTags)) {
+ return Blade::render(<<
+HTML);
+ }
+
+ return Blade::render(<<rootLayoutTags[0]}
+
+{$this->rootLayoutTags[1]}
+HTML);
+ }
+
+ public static function from(string $viewWithoutScript, string $path, array $rootLayoutTags)
+ {
+ /** @var ComponentHelper */
+ $componentHelper = app(ComponentHelper::class);
+
+ return new static($viewWithoutScript, $componentHelper->getTag($path), $rootLayoutTags);
+ }
+}
diff --git a/src/SpladeCoreServiceProvider.php b/src/SpladeCoreServiceProvider.php
index 8d53f61..8d9f0a7 100644
--- a/src/SpladeCoreServiceProvider.php
+++ b/src/SpladeCoreServiceProvider.php
@@ -103,10 +103,6 @@ protected function registerBladeCompiler()
$blade->component('dynamic-component', DynamicComponent::class);
});
});
-
- BladeCompiler::beforeCompilingString(
- fn ($value, $data, $path) => ExtractVueScriptFromBladeView::from($value, $data, $path)->handle(app('files'))
- );
}
protected function registerFactory()
diff --git a/src/View/BladeCompiler.php b/src/View/BladeCompiler.php
index 74f9995..98add46 100644
--- a/src/View/BladeCompiler.php
+++ b/src/View/BladeCompiler.php
@@ -2,7 +2,9 @@
namespace ProtoneMedia\SpladeCore\View;
+use Illuminate\Support\Str;
use Illuminate\View\Compilers\BladeCompiler as BaseBladeCompiler;
+use ProtoneMedia\SpladeCore\ExtractVueScriptFromBladeView;
class BladeCompiler extends BaseBladeCompiler
{
@@ -11,30 +13,34 @@ class BladeCompiler extends BaseBladeCompiler
*/
protected array $data = [];
- /**
- * Callbacks that are called before compiling the string.
- */
- protected static array $beforeCompilingStringCallbacks = [];
+ public array $hashes = [];
- /**
- * Registers a callback that is called before compiling the string.
- */
- public static function beforeCompilingString(callable $callback): void
- {
- static::$beforeCompilingStringCallbacks[] = $callback;
- }
+ public array $pendingViews = [];
/**
* Extract the Vue script from the given template.
*/
public function compileString($value): string
{
- foreach (static::$beforeCompilingStringCallbacks as $callback) {
- $callback = $callback->bindTo($this, static::class);
- $value = $callback($value, $this->data, $this->getPath());
+ $service = ExtractVueScriptFromBladeView::from($value, $this->data, $this->getPath());
+
+ $result = $service->handle($this->files);
+
+ if (is_string($result)) {
+ return parent::compileString($result);
}
- return parent::compileString($value);
+ // TODO: move to CompilerEngine::get()
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 9);
+
+ // prevent leaking the full path
+ $path = Str::after($trace[8]['file'], base_path());
+
+ $hash = md5($path.'.'.$trace[8]['line']);
+
+ $this->pendingViews[$hash] = $result->setOriginalView($value);
+
+ return parent::compileString($result->viewWithoutScript);
}
/**
diff --git a/src/View/CompilerEngine.php b/src/View/CompilerEngine.php
index c8b67c2..de15631 100644
--- a/src/View/CompilerEngine.php
+++ b/src/View/CompilerEngine.php
@@ -2,8 +2,11 @@
namespace ProtoneMedia\SpladeCore\View;
+use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\Str;
use Illuminate\View\Engines\CompilerEngine as BaseCompilerEngine;
use ProtoneMedia\SpladeCore\ComponentHelper;
+use ProtoneMedia\SpladeCore\PendingView;
class CompilerEngine extends BaseCompilerEngine
{
@@ -31,14 +34,36 @@ public function get($path, array $data = [])
if (str_contains($path, DIRECTORY_SEPARATOR.'components'.DIRECTORY_SEPARATOR)) {
$vueComponent = $this->componentHelper->getTag($path).'.vue';
+ // Delete the compiled script if the Vue component is not found,
+ // for example, when the compiled component is deleted but not
+ // the compiled template.
if (! $this->files->exists(config('splade-core.compiled_scripts').DIRECTORY_SEPARATOR.$vueComponent)) {
$this->files->delete($this->getCompiler()->getCompiledPath($path));
}
}
- return tap(
- parent::get($path, $data),
- fn () => $compiler->setData([])
- );
+ $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6);
+
+ // prevent leaking the full path
+ $tracePath = Str::after($trace[5]['file'], base_path());
+
+ $hash = md5($tracePath.'.'.$trace[5]['line']);
+
+ $result = parent::get($path, $data);
+ $compiler->setData([]);
+
+ // TODO: evaluate if $path contains