Skip to content

VuePre is a package to prerender vue templates using only PHP

License

Notifications You must be signed in to change notification settings

ctxcode/vue-pre

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

VuePre

VuePre is a package to prerender vue templates. This is useful for SEO and avoiding blank pages on page load. What VuePre does, is translating the Vue template to a PHP template, then replaces the javascript expressions with PHP expressions and caches it. Once cached, you can render thousands of templates per second depending on your hardware.

PROS

- Very fast
- No dependencies

CONS

- Some javascript expressions are not supported (yet).

Installation

composer require ctxkiwi/vue-pre

Basic usage

$vue = new \VuePre\Engine();
$vue->setCacheDirectory(__DIR__ . '/cache');

// Method 1
$data = ["name" => "world"];
$html = $vue->renderHtml('<div>Hello {{ name }}!</div>', $data);

// Method 2 - Using component directory (required if you use sub-components)
$vue->setComponentDirectory(__DIR__ . '/components');
$html = $vue->renderComponent('my-page', $data);

Component directory

// If you set your directory like this
$vue->setComponentDirectory(__DIR__ . '/components');
// It's going to look for any .php file and register the filename as a component
// So, if you have components/pages/homepage.php
// It will use this file for the <homepage> component

Component example

<?php
return [
    'beforeRender' => function (&$data) {
        $data['counter'] = 0;
    },
];
?>

<template>
    <div>
        <button v-on:click="min"> - </button>
        {{ counter }}
        <button v-on:click="plus"> + </button>
    </div>
</template>

<script>
    Vue.component('homepage', {
        template: '#vue-template-homepage',
        data: function () {
            return {
                counter: 0,
            };
        },
        methods: {
            plus: function(){ this.counter++; },
            min: function(){ this.counter--; },
        }
    });
</script>

Real world example

class View{
    public static function render($view, $data = []){
        // Normal PHP template engine
        ...
        return $html;
    }
    public static function renderComponent($name, $data = []){
        $vue = new \VuePre\Engine();
        $vue->setCacheDirectory(Path::get('tmp'). '/cache');
        $vue->setComponentDirectory(Path::get('views') . '/components');

        $html = $vue->renderComponent($name, $data);
        $templates = $vue->getTemplateScripts();
        $js = $vue->getJsScripts();
        $vueInstance = $vue->getVueInstanceScript('#app', $name, $data);

        $html = '<div id="app">'.$html.'</div>'.$templates.$js.$vueInstance;

        return static::render('layouts/main.html', ['CONTENT' => $html];
    }
}

class ViewController{
    public function homepage(){
        $data = [
            // Dont put private data in here, because it's shared with javascript
            'layoutData' => [
                'authUser' => \AuthUser::getUser()->getPublicData(),
            ],
            'featureProducts' => Product::where('featured', true)->limit(10)->get();
        ];
        // Render <homepage> component
        echo View::renderComponent('homepage', $data);
    }
}
<!-- views/layouts/main.html -->
<!DOCTYPE>
<html>
    <head>
        <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    </head>
    <body>
        {!! $CONTENT !!}
    <body>
</html>
<?php
// views/components/layout.php

return [
    'beforeRender' => function (&$data) {
        $data = $data['layout-data'];
    },
];
?>

<template>
    <div>
        <header>...</header>
        <main>
            <slot></slot>
        </main>
        <footer>...</footer>
    </div>
</template>

<script>
    Vue.component('layout', {
        props: ['layoutData'],
        template: '#vue-template-layout',
        data: function () {
            return this.layoutData;
        },
    });
</script>
<?php
// views/components/homepage.php
?>

<template>
    <layout :layout-data="layoutData">
        <div class="homepage">
            <h1>Welcome</h1>
            <p>...</p>
            <h2>Featured products</h2>
            <div v-for="product in featuredProducts"><h3>{{ product.name }}</h3></div>
        </div>
    </layout>
</template>

<script>
    Vue.component('homepage', {
        props: ['layoutData', 'featuredProducts'],
        template: '#vue-template-homepage',
        data: function () {
            return {};
        },
    });
</script>

Generating <scripts>

You can generate scripts for your component templates and your component.js files.

// Based on your last render
$vue->getScripts();
$vue->getTemplateScripts(); // only template scripts
$vue->getJsScripts(); // only js scripts

// By component name
$vue->getTemplateScript('my-page');
$vue->getJsScript('my-page');

// Usefull
$vue->getRenderedComponentNames();

API

->setCacheDirectory(String $path)
->setComponentDirectory(String $path)
->setGlobals(Array $globalVariables) // e.g. ['loggedIn' => true, 'user' => ['id'=>123, 'username'=>'TerryDavis']]
->renderHtml(String $html, Array $data)
->renderComponent(String $componentName, Array $data)

// Optional settings
->ignoreAttributes(Array $attributeNames)
->unignoreAttributes(Array $attributeNames)
->getIgnoredAttributes() : Array $attributeNames

// Generating scripts
->getScripts($idPrefix = 'vue-template-');
->getTemplateScripts($idPrefix = 'vue-template-');
->getTemplateScript(String $componentName, $default = null, $idPrefix = 'vue-template-');
->getJsScripts();
->getJsScript(String $componentName, $default = null);

// Others
->getComponentAlias(String $componentName, $default = null)
->getRenderedComponentNames();

JS expressions | Supported

# Prototype functions
.indexOf()
.length

# JS Functions
typeof()

# Values: variables, strings, numbers, booleans, null, objects, arrays, functions

# Comparisons
myVar === 'Hello'
something ? 'yes' : false

# Nested expressions
(((5 + 5) > 2) ? true : false) ? (myBool ? 'Yes' : 'Yez') : 'No'

# Objects
product.active ? product.name : product.category.name

# Methods using $vuePre->setMethods(['myFunc'=> function(){ ... }])
product.active ? myFunc(product.name) : null

Todos

Note: Feel free to make an issue for these, so i can make them a prority. The only reason these are not implemented yet is because of low priority.

  • Custom error handlers
  • Options:
    • ignoreVariableNotFound
    • ignoreVariableNames ignoreMethodNames
    • ignoreSubComponents ignoreSubComponentNames

Contributors

The DOM iterator code was partially copied from wmde/php-vuejs-templating

About

VuePre is a package to prerender vue templates using only PHP

Resources

License

Stars

Watchers

Forks

Packages

No packages published