From b1eb9e269b0217aa739374957684860b162e6f34 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 21 Dec 2023 23:45:56 +0100 Subject: [PATCH] Cleanup + improved JS object passing --- app/app/View/Components/ToVueProp.php | 2 +- .../views/components/to-vue-prop.blade.php | 2 +- app/tests/Browser/ToVuePropTest.php | 2 +- ...entsTest__it_builds_the_components__12.vue | 38 +++---- src/BladeViewExtractor.php | 10 +- src/ComponentSerializer.php | 100 ++++++------------ src/View/Factory.php | 26 +---- 7 files changed, 58 insertions(+), 122 deletions(-) diff --git a/app/app/View/Components/ToVueProp.php b/app/app/View/Components/ToVueProp.php index fefbf0e..2a55a55 100644 --- a/app/app/View/Components/ToVueProp.php +++ b/app/app/View/Components/ToVueProp.php @@ -40,7 +40,7 @@ public function __construct( #[VueProp] public array $defaultArray = ['foo'], #[VueProp] public array|bool|string $multipleTypes = ['foo'], #[VueProp(as: 'renamed')] public string $name = 'renamed-foo', - #[VuePropRaw] public string $json = '{"foo":"bar"}', + #[VuePropRaw] public string $jsObject = "{foo: 'bar'}", ) { } diff --git a/app/resources/views/components/to-vue-prop.blade.php b/app/resources/views/components/to-vue-prop.blade.php index 1586468..192c026 100644 --- a/app/resources/views/components/to-vue-prop.blade.php +++ b/app/resources/views/components/to-vue-prop.blade.php @@ -18,5 +18,5 @@

Multiple Types: ()

Data From Method: ()

Renamed: ()

-

JSON: ()

+

JS Object: ()

\ No newline at end of file diff --git a/app/tests/Browser/ToVuePropTest.php b/app/tests/Browser/ToVuePropTest.php index 48658fe..fd58417 100644 --- a/app/tests/Browser/ToVuePropTest.php +++ b/app/tests/Browser/ToVuePropTest.php @@ -31,7 +31,7 @@ public function it_can_pass_blade_props_as_vue_rops() ->assertSee('Multiple Types: [ "foo" ]') ->assertSee('Data From Method: [ "foo", "bar", "baz" ]') ->assertSee('Renamed: renamed-foo') - ->assertSee('JSON: { "foo": "bar" } (object)'); + ->assertSee('JS Object: { "foo": "bar" } (object)'); }); } } diff --git a/app/tests/Unit/Console/__snapshots__/BuildComponentsTest__it_builds_the_components__12.vue b/app/tests/Unit/Console/__snapshots__/BuildComponentsTest__it_builds_the_components__12.vue index a97b729..5358d6b 100644 --- a/app/tests/Unit/Console/__snapshots__/BuildComponentsTest__it_builds_the_components__12.vue +++ b/app/tests/Unit/Console/__snapshots__/BuildComponentsTest__it_builds_the_components__12.vue @@ -4,25 +4,25 @@ import { h } from 'vue' const props = defineProps({ spladeBridge: Object, spladeTemplateId: String, - mixed: { default: 'foo' }, - string: { type: String, default: 'foo' }, - defaultString: { type: String, default: 'foo' }, - nullableString: { type: String, default: null }, - int: { type: Number, default: null }, - bool: { type: Boolean, default: null }, - array: { type: Array, default: null }, - object: { type: Object, default: null }, - nullableInt: { type: Number, default: null }, - nullableBool: { type: Boolean, default: null }, - nullableArray: { type: Array, default: null }, - nullableObject: { type: Object, default: null }, - defaultInt: { type: Number, default: 1 }, - defaultBool: { type: Boolean, default: true }, - defaultArray: { type: Array, default: JSON.parse('[\u0022foo\u0022]') }, - multipleTypes: { type: [Array, String, Boolean], default: JSON.parse('[\u0022foo\u0022]') }, - renamed: { type: String, default: 'renamed-foo' }, - json: { default: JSON.parse('{\u0022foo\u0022:\u0022bar\u0022}') }, - dataFromMethod: { type: Array, default: null }, + mixed: {}, + string: { type: String }, + defaultString: { type: String }, + nullableString: { type: String }, + int: { type: Number }, + bool: { type: Boolean }, + array: { type: Array }, + object: { type: Object }, + nullableInt: { type: Number }, + nullableBool: { type: Boolean }, + nullableArray: { type: Array }, + nullableObject: { type: Object }, + defaultInt: { type: Number }, + defaultBool: { type: Boolean }, + defaultArray: { type: Array }, + multipleTypes: { type: [Array, String, Boolean] }, + renamed: { type: String }, + jsObject: {}, + dataFromMethod: { type: Array }, }) const spladeRender = h({ name: 'SpladeComponentToVuePropRender', diff --git a/src/BladeViewExtractor.php b/src/BladeViewExtractor.php index b60da2f..6915dcd 100644 --- a/src/BladeViewExtractor.php +++ b/src/BladeViewExtractor.php @@ -6,11 +6,9 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Process; -use Illuminate\Support\Js; use Illuminate\Support\Str; use InvalidArgumentException; use ProtoneMedia\SpladeCore\Facades\SpladePlugin; -use ProtoneMedia\SpladeCore\View\Factory; class BladeViewExtractor { @@ -321,19 +319,13 @@ protected function extractDefinePropsFromScript(): array { $bladePropsAsVueProps = Collection::make($this->getBladePropsThatArePassedAsVueProps()) ->map(function (object $specs) { - $default = $specs->raw - ? Factory::convertJsonToJavaScriptExpression($specs->default) - : Js::from($specs->default)->toHtml(); - $type = null; if (! $specs->raw) { $type = is_array($specs->type) ? '['.implode(',', $specs->type).']' : "{$specs->type}"; } - return $type - ? "{type: {$type}, default: {$default}}" - : "{default: {$default}}"; + return $type ? "{type: {$type}}" : '{}'; }); $defaultProps = Collection::make(['spladeTemplateId' => 'String']) diff --git a/src/ComponentSerializer.php b/src/ComponentSerializer.php index 3f047d5..e4cd9c4 100644 --- a/src/ComponentSerializer.php +++ b/src/ComponentSerializer.php @@ -13,6 +13,7 @@ use ProtoneMedia\SpladeCore\Attributes\VueProp; use ProtoneMedia\SpladeCore\Attributes\VuePropRaw; use ProtoneMedia\SpladeCore\Attributes\VueRef; +use ProtoneMedia\SpladeCore\Facades\Transformer; use ReflectionAttribute; use ReflectionClass; use ReflectionMethod; @@ -83,6 +84,34 @@ public function toArray(array $with = [], bool $resolveImmediately = false): arr ); } + /** + * Transforms a Blade Component prop. + */ + private function transformValue(mixed $value): mixed + { + $value = Transformer::handle($value); + + if ($value instanceof Model) { + return (object) $value->jsonSerialize(); + } + + if ($value instanceof Collection) { + return $value->map(function ($item) { + if ($item instanceof Model) { + return (object) $item->jsonSerialize(); + } + + return $item; + })->jsonSerialize(); + } + + if ($value instanceof JsonSerializable) { + return $value->jsonSerialize(); + } + + return $value; + } + /** * Gathers all data that will be two-way bound to the Vue component. */ @@ -107,27 +136,9 @@ public function getDataFromProperties(): array continue; } - $value = $property->getValue($this->component); - - if ($value instanceof Model) { - $value = (object) $value->jsonSerialize(); - } - - if ($value instanceof Collection) { - $value = $value->map(function ($item) { - if ($item instanceof Model) { - return (object) $item->jsonSerialize(); - } - - return $item; - })->jsonSerialize(); - } - - if ($value instanceof JsonSerializable) { - $value = $value->jsonSerialize(); - } - - $values[$name] = $this->getSerializedPropertyValue($value); + $values[$name] = $this->getSerializedPropertyValue( + $property->getValue($this->component) + ); } return $values; @@ -192,7 +203,6 @@ public static function getPropsFromComponentClass(string $componentClass): array // From Properties... $properties = (new ReflectionClass($componentClass))->getProperties(); - $constructorParameters = (new ReflectionClass($componentClass))->getConstructor()?->getParameters(); foreach ($properties as $property) { if ($property->isStatic() || ! $property->isPublic()) { @@ -213,16 +223,7 @@ public static function getPropsFromComponentClass(string $componentClass): array $as = $vuePropAttribute->getArguments()['as'] ?? $name; - $defaultValue = $property->getDefaultValue(); - - $constructorParameter = collect($constructorParameters)->first(fn ($parameter) => $parameter->getName() === $name); - - if ($constructorParameter?->isDefaultValueAvailable()) { - $defaultValue = $constructorParameter->getDefaultValue(); - } - $values[$as] = (object) [ - 'default' => $defaultValue, 'raw' => $vuePropAttribute->getName() === VuePropRaw::class, 'type' => static::mapTypeToVueType($property->getType()), 'value' => null, @@ -253,7 +254,6 @@ public static function getPropsFromComponentClass(string $componentClass): array $as = $vuePropAttribute->getArguments()['as'] ?? $name; $values[$as] = (object) [ - 'default' => null, 'raw' => $vuePropAttribute->getName() === VuePropRaw::class, 'type' => static::mapTypeToVueType($method->getReturnType()), 'value' => null, @@ -264,7 +264,7 @@ public static function getPropsFromComponentClass(string $componentClass): array } /** - * Returns all public props from the component class. + * Returns all public props from the component class (one-way bound). */ public function getPropsFromComponent(): array { @@ -272,7 +272,6 @@ public function getPropsFromComponent(): array // From Properties... $properties = (new ReflectionClass($this->component))->getProperties(); - $constructorParameters = (new ReflectionClass($this->component))->getConstructor()?->getParameters(); foreach ($properties as $property) { if ($property->isStatic() || ! $property->isPublic()) { @@ -297,40 +296,10 @@ public function getPropsFromComponent(): array ? $property->getValue($this->component) : null; - if ($value instanceof Model) { - $value = (object) $value->jsonSerialize(); - } - - if ($value instanceof Collection) { - $value = $value->map(function ($item) { - if ($item instanceof Model) { - return (object) $item->jsonSerialize(); - } - - return $item; - })->jsonSerialize(); - } - - if ($value instanceof JsonSerializable) { - $value = $value->jsonSerialize(); - } - - $defaultValue = $property->hasDefaultValue() - ? $property->getDefaultValue() - : null; - - $constructorParameter = collect($constructorParameters) - ->first(fn ($parameter) => $parameter->getName() === $name); - - if ($constructorParameter?->isDefaultValueAvailable()) { - $defaultValue = $constructorParameter->getDefaultValue(); - } - $values[$as] = (object) [ - 'default' => $defaultValue, 'raw' => $vuePropAttribute->getName() === VuePropRaw::class, 'type' => static::mapTypeToVueType($property->getType()), - 'value' => $this->getSerializedPropertyValue($value), + 'value' => $this->getSerializedPropertyValue($this->transformValue($value)), ]; } @@ -358,7 +327,6 @@ public function getPropsFromComponent(): array $as = $vuePropAttribute->getArguments()['as'] ?? $name; $values[$as] = (object) [ - 'default' => null, 'raw' => $vuePropAttribute->getName() === VuePropRaw::class, 'type' => static::mapTypeToVueType($method->getReturnType()), 'value' => Container::getInstance()->call([$this->component, $name]), diff --git a/src/View/Factory.php b/src/View/Factory.php index 3a7a7b3..a67ce7d 100644 --- a/src/View/Factory.php +++ b/src/View/Factory.php @@ -13,8 +13,6 @@ class Factory extends BaseFactory { - const REQUIRED_JSON_FLAGS = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_THROW_ON_ERROR; - /** * Whether to track Splade components. */ @@ -97,28 +95,6 @@ public function pushSpladeTemplate($id, $value): void ); } - /** - * Convert the given JSON to a JavaScript expression. - * - * @param string $json - * @param int $flags - * @return string - * - * @throws \JsonException - */ - public static function convertJsonToJavaScriptExpression($json, $flags = 0) - { - if ($json === '[]' || $json === '{}') { - return $json; - } - - if (Str::startsWith($json, ['"', '{', '['])) { - return "JSON.parse('".substr(json_encode($json, $flags | static::REQUIRED_JSON_FLAGS), 1, -1)."')"; - } - - return $json; - } - /** * Temporarily store the passed attributes, render the component and * push it to the Splade templates stack. Then return a generic Vue @@ -178,7 +154,7 @@ public function renderComponent() } $attributes[$key] = $specs->raw - ? static::convertJsonToJavaScriptExpression($specs->value) + ? $specs->value : Js::from($specs->value)->toHtml(); });