├── LICENSE.md
├── README.md
├── composer.json
├── docs
├── simple-vue-instance.md
└── toggling-vue-elements.md
├── logo.png
└── src
└── VueComponentServiceProvider.php
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) Appstract
4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in
13 | > all copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | > THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | ## What is Laravel Vue Blade Directive?
4 |
5 | Originally inspired by [Faking Server-Side Rendering With Vue.js and Laravel by Anthony Gore](https://vuejsdevelopers.com/2017/04/09/vue-laravel-fake-server-side-rendering/), Laravel Vue Blade Directive package is meant to provide tools to build static PHP and Vue Templates in tandem.
6 |
7 | This is not meant to replace a full SSR application, but to facilitate jankless Vue Components within Blade Templates. For example, a dynamically updated sidebar component, that is statically rendered by PHP on first load.
8 |
9 | The goal instead is to be capable of writing a single Component in a blade file, include it with `@vueComponent(sidebar)` and have it dynamically produce both the static PHP, and the vuejs template. This will then cleanly hydrate on the Vue instance with no jank.
10 |
11 |
12 | ## Proof Of Concept Demo
13 |
14 | [Original POC Repo](https://github.com/unr/laravel-vue-hydrate)
15 |
16 | 
17 |
18 | ## Installation
19 |
20 | You can install the package via composer:
21 |
22 | ```bash
23 | composer require lootmarket/laravel-vue-blade-component
24 | ```
25 |
26 | ### Provider
27 |
28 | Then add the ServiceProvider to your `config/app.php` file:
29 |
30 | ```php
31 | 'providers' => [
32 | ...
33 |
34 | LootMarket\VueComponent\VueComponentServiceProvider::class
35 |
36 | ...
37 | ];
38 | ```
39 |
40 | ## How to Use Laravel Vue Blade Directive
41 |
42 | ### Guides
43 |
44 | 1. [A Simple Vue Instance](docs/simple-vue-instance.md)
45 | 1. [Toggling an Element with v-if && v-show](docs/toggling-vue-elements.md)
46 |
47 | ### Documentation
48 |
49 | Super lighteweight documentation below. Please let us know if there's something more descriptive you needed.
50 |
51 | #### `@vueComponent(, )`
52 |
53 | Blade Directive for rendering our Vue Components.
54 |
55 | **component-name** file name of the component to be rendered.
56 |
57 | **path** optionally provide a path to the component if nested in a subfolder.
58 |
59 | usage example:
60 |
61 | ```blade
62 | @vueComponent(App)
63 |
64 | this loads resources/views/App.blade.php
65 |
66 | @vueComponent(App, vue/routes)
67 |
68 | this loads resources/views/vue/routes/App.blade.php
69 | ```
70 |
71 | Templates loaded via `@vueComponent` will have `$vue` passed to them as a boolean. Additionally, they will be pushed to a [stack](https://laravel.com/docs/5.4/blade#stacks) called `vue`
72 |
73 |
74 |
75 | #### `@vue(, )`
76 |
77 | Blade Directive for displaying `{{ variableFromVueJS }}` or `$phpVariable` written to dom. This is used within a `@vueComponent` file.
78 |
79 | If `$vue` is true, will simply echo out the php variable passed. If `$vue` is false, will echo out a string to be interpreted within a javascript template.
80 |
81 | usage example:
82 |
83 | ```blade
84 | @vue('$store.state.username', $initialState['username'])
85 | ```
86 |
87 | When used within a `@vueComponent()` template, it will return:
88 |
89 | the result of `` to the dom.
90 |
91 | the string `{{ $store.state.username }}` to the js template.
92 |
93 | This allows us to declare a vuex variable in vue template, and echo out the initial state to the server rendered php.
94 |
95 |
96 | #### `@v()`
97 |
98 | A simple blade directive for taking a string, and echoing it with `{{ string }}` to the dom. This allows us to easily write in js variables for our vue templates.
99 |
100 | This is used by `@vue()`, but can be called directly if needed.
101 |
102 | usage example:
103 |
104 | ```blade
105 | @v($store.state.username)
106 | ```
107 |
108 | this will simply return the string `{{ $store.state.username }}` to be used within vue.
109 |
110 | #### `@stack('vue')`
111 |
112 | This is a native function with Laravel 5.4 Blades - it will render out the js template for each component passed to `@vueComponent()`
113 |
114 | Each component is only rendered once, not multiple times if done in a loop.
115 |
116 |
117 | ## About LootMarket
118 |
119 | LootMarket is a development team based in Toronto, Ontario focused on creating great experiences around esports. In our quest for the ultimate PHP & Vue experience, we've created this.
120 |
121 | ## License
122 |
123 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
124 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lootmarket/laravel-vue-blade-component",
3 | "description": "Jankless Vue Components in Laravel Blade Templates",
4 | "keywords": [
5 | "lootmarket",
6 | "laravel-vue-blade-component",
7 | "laravel-vue",
8 | "jankless"
9 | ],
10 | "homepage": "https://github.com/lootmarket/laravel-vue-blade-component",
11 | "license": "MIT",
12 | "authors": [
13 | {
14 | "name": "Paul Morrison",
15 | "email": "paul@lootmarket.com",
16 | "homepage": "https://lootleague.com",
17 | "role": "Developer"
18 | }
19 | ],
20 | "require": {
21 | "php": ">=5.6"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "LootMarket\\VueComponent\\": "src"
26 | }
27 | },
28 | "config": {
29 | "sort-packages": true
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/simple-vue-instance.md:
--------------------------------------------------------------------------------
1 | # Creating a simple Vue Instance
2 |
3 | Assuming you've already installed via composer, and added the ServiceProvider...
4 |
5 | ## Vue Setup
6 |
7 | Assuming you've installed Vue & Vuex with npm, and are using Laravel Mix... You should be able to set up your vuejs app like so.
8 |
9 | ```js
10 | window.Vue = require('vue');
11 | window.Vuex = require('vuex');
12 |
13 | const store = new Vuex.Store({
14 | state: {
15 | username: null,
16 | },
17 | });
18 |
19 | const App = new Vue({
20 | name: 'App',
21 | el: '#app',
22 | template: '#app-template',
23 | store,
24 | });
25 | ```
26 |
27 | This will create a basic state, where we're expecting a username variable to be set. However, we're missing the `#app-template` template, as well as the intial data to be populated in username.
28 |
29 | Assuming this file is built to `/public/app.js` add it to your template with
30 |
31 | ```blade
32 |
33 | ```
34 |
35 |
36 | ## Creating our template.
37 |
38 | We need to create a VueComponent template with the id `app-template` to be picked up by the js app. Our goal is to make a single blade file, that will render to both a JS Template, and staticly in the view.
39 |
40 | Creating `resources/views/app.blade.php` to be used as our component file.
41 |
42 | > When we include our component with `@vueComponent`, it is passed `$vue` as a boolean, depending on if its meant to be php, or a vue js template.
43 |
44 | ```blade
45 |
46 | hello.
47 |
48 | ```
49 |
50 | Here we've done two things.
51 |
52 | 1. *We created a single wrapping element with our matching element ID.* This will sync our dom w/ our javascript vue app above.
53 |
54 | 2. For our main vue element, we also set *server rendered true* on the javascript instance. This will [Allow vue to hydrate, not replace the existing dom](https://github.com/vuejs/vue-ssr-docs/blob/master/en/hydration.md)
55 |
56 | Now, to include this template in our view.
57 |
58 | ```blade
59 | @vueComponent(app)
60 | ```
61 |
62 | This will render our div in the source (however if your Vue instance is running, it will remove the dom since we're still missing the JS Template)
63 |
64 | ```html
65 |
66 | hello.
67 |
68 | ```
69 |
70 | ## Including the JS Template
71 |
72 | When you use `@vueComponent(app)` it will render the PHP inline. As well, it will push the js template to a stack called `vue`. You can add the following to your blade template, and it will render out the scripts pushed to the that stack.
73 |
74 | ```blade
75 | @stack('vue')
76 | ```
77 |
78 | After you've also rendered out the vue template, you should see `hello` rendered in your view. If you view source, you should see both the static rendered component from the previous step, and this JS Template.
79 |
80 | ```html
81 |
85 | ```
86 |
87 | > The `@vueComponent(app)` command automatically wrapped the element with `id='{component-name}-app'`
88 |
89 |
90 |
91 | ---
92 |
93 | At this point, with the above written...
94 |
95 | Your vue instance should init, and mount over the div with hello. No VueJS Errors.
96 |
97 | ---
98 |
99 | ## Rendering a variable from the server in VueX
100 |
101 | We need to pass a state object to our vue. This `$initialState` will include the items to be expected in the Vuex State, in the same format. When this is passed to the view, it will replace the default vuex state.
102 |
103 | Lets set up an example object, returned to our view like...
104 |
105 | ```php
106 | Route::get('/', function () {
107 | // matches our VueX State, preventing jank when Vue Mounts
108 | $initial_state = [
109 | 'username' => 'Cool Guy From PHP',
110 | ];
111 |
112 | return view('welcome')
113 | ->with('initial_state', $initial_state);
114 | });
115 | ```
116 | This provides the necessary data to the view. We can now use `@vue()` to echo a variable to our php/js rendered components. We can update `app.blade.php` to:
117 |
118 | ```blade
119 |
120 | hello, my name is @vue('$store.state.username', $initial_state['username'])
121 |
122 | ```
123 |
124 | *In our JS Template*, we will echo `{{ $store.state.username }}` for the vue instance.
125 |
126 | *In our PHP Template*, we will echo `Cool Guy From PHP` directly into the source.
127 |
128 | You should now see your rendered source like:
129 |
130 | ```html
131 |
132 | hello, my name is Cool Guy From PHP
133 |
134 |
135 |
148 |
149 | ```
150 |
151 | Now using that global `__INITIAL_STATE__` var, we can replace our vuex state. Lets update our app.js to:
152 |
153 | ```js
154 | window.Vue = require('vue');
155 | window.Vuex = require('vuex');
156 |
157 | const store = new Vuex.Store({
158 | state: {
159 | username: null,
160 | },
161 | });
162 |
163 | // the magic, where we update our state.
164 | if (window.__INITIAL_STATE__) {
165 | store.replaceState(JSON.parse(window.__INITIAL_STATE__));
166 | }
167 |
168 | const testApp = new Vue({
169 | name: 'TestAppRoot',
170 | el: '#app',
171 | template: '#app-template',
172 | store,
173 | });
174 |
175 | ```
176 |
177 | Now that our Vuex state, will match our php rendered var, you should see `hello, my name is Cool Guy From PHP`
178 |
179 |
180 | ---
181 |
182 | At this point, you've now created a super simple vue app, that is rendered statically in PHP, as well as mounted jank-free into Vue & Vuex.
183 |
184 | At this point, your app is reactive and can be updated through user interaction like normal. For example...
185 |
186 | ---
187 |
188 | ## A super basic 'updating variable' example.
189 |
190 | Lets go back to our test app instance, and add an update on `created()`, and change the variable via VueX.
191 |
192 | ```js
193 | window.Vue = require('vue');
194 | window.Vuex = require('vuex');
195 |
196 | const store = new Vuex.Store({
197 | state: {
198 | username: null,
199 | },
200 | mutations: {
201 | changeUsername(state) {
202 | state.username = 'Really, Really Cool Guy From JS';
203 | },
204 | },
205 | });
206 |
207 | // PHP has set __INITIAL_STATE__ to match our vuex store / data rendered live.
208 | if (window.__INITIAL_STATE__) {
209 | store.replaceState(JSON.parse(window.__INITIAL_STATE__));
210 | }
211 |
212 | const testApp = new Vue({
213 | name: 'TestAppRoot',
214 | el: '#app',
215 | template: '#app-template',
216 | store,
217 | created() {
218 | setTimeout(() => {
219 | store.commit('changeUsername');
220 | }, 2000);
221 | },
222 | });
223 | ```
224 | With this example, you should now see `hello, my name is Cool Guy From PHP`
225 |
226 | And after 2 seconds it updates to `hello, my name is Really, Really Cool Guy From JS`
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
--------------------------------------------------------------------------------
/docs/toggling-vue-elements.md:
--------------------------------------------------------------------------------
1 | # Toggling an Element with v-if && v-show
2 |
3 | The goal here is to toggle an element in php, as well as in vue using a `@vueComponent()`
4 |
5 | There are two types of 'element toggles' in Vue. `v-if` and `v-show`.
6 |
7 | 1. `v-if` will remove/add the element from the dom.
8 |
9 | 2. `v-show` will use css to toggle the elements state.
10 |
11 | > With laravel-vue-blade-components `v-show` is much easier/cleaner to handle than a `v-if` at this time.
12 |
13 |
14 |
15 | ## Toggling an element with `v-if`
16 |
17 | The vue part is quite simple, in our component:
18 |
19 | ```blade
20 | @if ($vue)
21 | User is an admin!
22 | User is NOT an admin!
23 | @endif
24 | ```
25 |
26 | In our vue template, this will now swap the span in the dom when set up. We however need to make this do the same for PHP when `$vue` is false.
27 |
28 | To do so, we need to use an elseif & check the same variable in PHP. using the above example of user.admin...
29 |
30 |
31 | ```blade
32 | @if ($vue)
33 | User is an admin!
34 | User is NOT an admin!
35 | @elseif (!$vue && $initialState['user']['admin'])
36 | User is an admin!
37 | @else
38 | User is NOT an admin!
39 | @endif
40 | ```
41 |
42 | Doing this, will result in:
43 |
44 | 1. Only one span initially rendered by PHP.
45 | 2. VueJS will happily mount onto this, as `v-if` would render only one span.
46 | 3. Template resumes as normaly, quietly hydrating.
47 |
48 |
49 | ## Toggling an element with `v-show`
50 |
51 | Since v-show is based on css, we need to toggle the element using `style="display:none;"` instead of preventing the element from being rendered.
52 |
53 | Using the same user is admin example, we can accomplish this like so:
54 |
55 | ```blade
56 | User is an admin!
57 | User is NOT an admin!
58 | ```
59 |
60 | Doing this, will result in:
61 |
62 | 1. Only one span initially shown, thanks to `style="display:none;"`
63 | 2. VueJS will happily mount onto this, since elements will match in both php && js templates.
64 | 3. Template resumes as normaly, quietly hydrating.
65 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lootmarket/laravel-vue-blade-component/22aac1a4e6769ddc60c2e34a4a4b50c1a7095a0a/logo.png
--------------------------------------------------------------------------------
/src/VueComponentServiceProvider.php:
--------------------------------------------------------------------------------
1 | $pushKey)): \$__env->$pushKey = 1; \$__env->startPush('vue');
35 | echo '';
38 | \$__env->stopPush(); endif;
39 |
40 | echo \$__env->make('{$path}/{$componentName}', array_except(get_defined_vars(), ['__data', '__path']))->with(['vue' => false])->render();
41 | ?>";
42 | });
43 |
44 |
45 | /**
46 | * vue Blade Directive
47 | * Will include a different variable type based on being vue or not.
48 | * Expects $vue to be a boolean
49 | */
50 | Blade::directive('vue', function ($expression) {
51 | list($vueVariable, $phpVariable) = explode(', ', $expression);
52 | return "{{ \$vue ? @v($vueVariable) : $phpVariable }}";
53 | });
54 |
55 | /**
56 | * v(ariable) Blade Directive
57 | * Takes a 'string' and returns it as {{ string }} for use in vue templates.
58 | * Called by @vue() directive, but can be used standalone as @v()
59 | */
60 | Blade::directive('v', function($expression) {
61 | return "";
62 | });
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------