├── 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 | ![Example Of Jankless Vue Component](https://camo.githubusercontent.com/d217ca1d6120a7adc217027bb4f38e948eba237c/687474703a2f2f756e722e696d2f3244315932773048316e33722f636f6e74656e74) 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 | --------------------------------------------------------------------------------