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

Commit

Permalink
Merge pull request #20 from protonemedia/resolve-once
Browse files Browse the repository at this point in the history
Cleanup + improved JS object passing
  • Loading branch information
pascalbaljet authored Dec 21, 2023
2 parents e8844c0 + b1eb9e2 commit a5367f4
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 122 deletions.
2 changes: 1 addition & 1 deletion app/app/View/Components/ToVueProp.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'}",
) {
}

Expand Down
2 changes: 1 addition & 1 deletion app/resources/views/components/to-vue-prop.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@
<p>Multiple Types: <span v-text="multipleTypes" /> (<span v-text="typeof multipleTypes" />)</p>
<p>Data From Method: <span v-text="dataFromMethod" /> (<span v-text="typeof dataFromMethod" />)</p>
<p>Renamed: <span v-text="renamed" /> (<span v-text="typeof renamed" />)</p>
<p>JSON: <span v-text="json" /> (<span v-text="typeof json" />)</p>
<p>JS Object: <span v-text="jsObject" /> (<span v-text="typeof jsObject" />)</p>
</div>
2 changes: 1 addition & 1 deletion app/tests/Browser/ToVuePropTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)');
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
10 changes: 1 addition & 9 deletions src/BladeViewExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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'])
Expand Down
100 changes: 34 additions & 66 deletions src/ComponentSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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;
Expand Down Expand Up @@ -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()) {
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -264,15 +264,14 @@ 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
{
$values = [];

// From Properties...
$properties = (new ReflectionClass($this->component))->getProperties();
$constructorParameters = (new ReflectionClass($this->component))->getConstructor()?->getParameters();

foreach ($properties as $property) {
if ($property->isStatic() || ! $property->isPublic()) {
Expand All @@ -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)),
];
}

Expand Down Expand Up @@ -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]),
Expand Down
26 changes: 1 addition & 25 deletions src/View/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -178,7 +154,7 @@ public function renderComponent()
}

$attributes[$key] = $specs->raw
? static::convertJsonToJavaScriptExpression($specs->value)
? $specs->value
: Js::from($specs->value)->toHtml();
});

Expand Down

0 comments on commit a5367f4

Please sign in to comment.