Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
Support for dynamic components
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalbaljet committed Jan 15, 2024
1 parent c1e5a2c commit 0e83be3
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 37 deletions.
3 changes: 3 additions & 0 deletions app/resources/views/dynamic-component-import.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<x-layout>
<x-dynamic-component-import />
</x-layout>
1 change: 1 addition & 0 deletions app/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Route::view('/blade-method', 'blade-method');
Route::view('/blade-method-callbacks', 'blade-method-callbacks');
Route::view('/component-import', 'component-import');
Route::view('/dynamic-component-import', 'dynamic-component-import');
Route::view('/change-blade-prop', 'change-blade-prop');
Route::view('/dynamic', 'dynamic')->withoutMiddleware(SubstituteBindings::class);
Route::view('/form', 'form');
Expand Down
19 changes: 15 additions & 4 deletions app/tests/Browser/ComponentImportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@

class ComponentImportTest extends DuskTestCase
{
/** @test */
public function it_handles_js_imports()
public static function urls()
{
$this->browse(function (Browser $browser) {
$browser->visit('/component-import')
return [
['/component-import'],
['/dynamic-component-import'],
];
}

/**
* @dataProvider urls
*
* @test */
public function it_handles_js_imports($url)
{
$this->browse(function (Browser $browser) use ($url) {
$browser->visit($url)
->assertMissing('h2')
->assertMissing('#headlessui-portal-root')
->press('Open Dialog')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup>
import { GenericSpladeComponent } from '@protonemedia/laravel-splade-core'
import { h, ref } from 'vue'
import { h, markRaw, ref } from 'vue'
const props = defineProps({ spladeBridge: Object, spladeTemplateId: String })
import { Dialog, DialogPanel, TransitionRoot, TransitionChild } from '@headlessui/vue'
Expand All @@ -13,10 +13,16 @@ function show() {
const spladeRender = h({
name: 'SpladeComponentDynamicComponentImportRender',
components: { GenericSpladeComponent, Dialog, DialogPanel, TransitionRoot, TransitionChild },
components: { GenericSpladeComponent, Dialog, DialogPanel },
template: spladeTemplates[props.spladeTemplateId],
data: () => {
return { ...props, openend, show }
return {
...props,
openend,
show,
TransitionRoot: markRaw(TransitionRoot),
TransitionChild: markRaw(TransitionChild),
}
},
})
</script>
Expand Down
69 changes: 51 additions & 18 deletions src/BladeViewExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class BladeViewExtractor

protected ScriptParser $scriptParser;

protected ?array $importedComponents = null;

public function __construct(
protected readonly string $originalView,
protected readonly array $data,
Expand Down Expand Up @@ -353,6 +355,7 @@ protected function renderImports(): string
{
$vueFunctionsImports = $this->scriptParser->getVueFunctions()
->push('h')
->when($this->getImportedComponents()['dynamic']->isNotEmpty(), fn ($collection) => $collection->push('markRaw'))
->when($this->needsSpladeBridge(), fn ($collection) => $collection->push('ref'))
->when($this->isRefreshable(), fn ($collection) => $collection->push('inject'))
->when($this->isComponent() && ! empty($this->getBladeProperties()), fn ($collection) => $collection->push('computed'))
Expand Down Expand Up @@ -465,6 +468,46 @@ protected function renderElementRefStoreAndSetter(): string
JS;
}

/**
* Returns the imported components.
*/
protected function getImportedComponents(): array
{
if ($this->importedComponents) {
return $this->importedComponents;
}

$this->importedComponents = [
'static' => Collection::make([]),
'dynamic' => Collection::make([]),
];

if (! $this->isComponent()) {
return $this->importedComponents;
}

Collection::make($this->scriptParser->getImports())
->keys()
->each(function (string $import) {
if (Str::contains($this->viewWithoutScriptTag, "<{$import}")) {
return $this->importedComponents['static'][] = $import;
}

// match anything in :is="" (e.g.: :is="true ? A : B") attribute
preg_match_all('/:is="(.+?)"/', $this->viewWithoutScriptTag, $matches);

$isDynamic = Collection::make($matches[1] ?? [])
->flatMap(fn (string $match) => explode(' ', $match))
->contains($import);

if ($isDynamic) {
return $this->importedComponents['dynamic'][] = $import;
}
});

return $this->importedComponents;
}

/**
* Renders the SpladeRender 'h'-function.
*/
Expand All @@ -474,32 +517,22 @@ protected function renderSpladeRenderFunction(): string
inheritAttrs: false,
JS : '';
$importedComponents = $this->getImportedComponents();
$dataObject = Collection::make(['...props'])
->merge($this->getBladeProperties())
->merge($this->scriptParser->getVariables()->reject(fn ($variable) => $variable === 'props'))
->merge($this->getBladeFunctions())
->when($this->isRefreshable(), fn (Collection $collection) => $collection->push('refreshComponent'))
->when($this->viewUsesElementRefs(), fn (Collection $collection) => $collection->push('setSpladeRef'))
->merge($importedComponents['dynamic']->map(function (string $component) {
return "{$component}: markRaw({$component})";
}))
->implode(',');
$components = Collection::make(
$this->isComponent() ? $this->scriptParser->getImports() : []
)
->keys()
->filter(function (string $import) {
if (Str::contains($this->viewWithoutScriptTag, "<{$import}")) {
return true;
}
// match anything in :is="" (e.g.: :is="true ? A : B") attribute
preg_match_all('/:is="(.+?)"/', $this->viewWithoutScriptTag, $matches);
return Collection::make($matches[1] ?? [])
->flatMap(fn (string $match) => explode(' ', $match))
->contains('TransitionRoot');
})
->prepend('GenericSpladeComponent')
->implode(',');
$components = Collection::make([
'GenericSpladeComponent', ...$importedComponents['static'],
])->implode(',');
$componentsObject = $this->isComponent() ? <<<JS
components: { {$components} },
Expand Down
13 changes: 1 addition & 12 deletions src/ScriptParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Peast\Syntax\Node\CallExpression;
use Peast\Syntax\Node\FunctionDeclaration;
use Peast\Syntax\Node\Identifier;
use Peast\Syntax\Node\ImportDeclaration;
use Peast\Syntax\Node\ObjectExpression;
use Peast\Syntax\Node\ObjectPattern;
use Peast\Syntax\Node\Program;
Expand All @@ -24,8 +25,6 @@ class ScriptParser

protected string $script;

protected array $imports = [];

protected array $vueFunctions = [
'computed',
'inject',
Expand Down Expand Up @@ -58,16 +57,6 @@ public function __construct(string $script)
])->parse();
}

/**
* Removes the import statements from the script.
*/
protected function removeImports(string $script): string
{
return Collection::make(explode(PHP_EOL, $script))
->filter(fn ($line) => ! str_starts_with(trim($line), 'import '))
->implode(PHP_EOL);
}

/**
* Returns all Vue functions that are used in the script.
*/
Expand Down

0 comments on commit 0e83be3

Please sign in to comment.