Giter Site home page Giter Site logo

exodusanto / laravel-ssr Goto Github PK

View Code? Open in Web Editor NEW
19.0 2.0 3.0 396 KB

Vue Server Side Rendering with Laravel + Node

PHP 92.38% Vue 3.23% HTML 4.39%
vue-server-renderer vue laravel laravel-ssr laravel-server-side-render server-side-rendering

laravel-ssr's Introduction

Laravel SSR

Dependencies

  • Php
  • Node

Clone this repo and use it like a boilerplate

How to install

  • Laravel base install
composer install
...
  • Node modules
yarn install | npm install
  • Build vue
yarn prod | npm run prod
  • Start node server
node render_server/server.js
  • Set laravel variable in the .env file
APP_RENDER=http://localhost:5005
  • Serve laravel
php artisan serve

How it works

larave-ssr

Bug

687474703a2f2f64726f70732e6b796c65666f782e63612f31637147502b

Just kidding. Please create an issue. Your issue is much more likely to be resolved/merged if it includes a fix & pull request.

Have an idea that improves laravel-ssr? Awesome! Please fork this repository, implement your idea (including documentation, if necessary), and submit a pull request.

laravel-ssr's People

Contributors

dependabot[bot] avatar exodusanto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

laravel-ssr's Issues

have u successfully use VueSSRClientPlugin before?

i see there is an unassigned var webpack.server.js

which is used in vue ssr at this link

ive havent have luck using on it coz even though everything is ok when i run yarn prod and yarn run:server

this gives me error as such

SyntaxError: Unexpected token :
    at getCompiledScript (C:\Users\uriah\sites\www\vuetified-ssr\node_modules\vue-server-renderer\build.js:8122:18)
    at evaluateModule (C:\Users\uriah\sites\www\vuetified-ssr\node_modules\vue-server-renderer\build.js:8137:18)
    at C:\Users\uriah\sites\www\vuetified-ssr\node_modules\vue-server-renderer\build.js:8191:17
    at Promise (<anonymous>)
    at C:\Users\uriah\sites\www\vuetified-ssr\node_modules\vue-server-renderer\build.js:8189:14
    at Object.renderToStream (C:\Users\uriah\sites\www\vuetified-ssr\node_modules\vue-server-renderer\build.js:8397:9)
    at VueSSR.RenderToStream (C:\Users\uriah\sites\www\vuetified-ssr\render_server\vue-ssr\renderer.js:115:44)
    at VueSSR.render (C:\Users\uriah\sites\www\vuetified-ssr\render_server\vue-ssr\renderer.js:110:14)
    at main (C:\Users\uriah\sites\www\vuetified-ssr\render_server\routers\view.js:35:19)
    at Layer.handle [as handle_request] (C:\Users\uriah\sites\www\vuetified-ssr\node_modules\express\lib\router\layer.js:95:5)
__vue_ssr_bundle__:2
  "entry": "main.build.js",

Anyway have configure the following files
views.js
renderer.js
webpack.server.js
and webpack.mix.js

and for the record im using latest laravel 5.5 vue, vuex , vue router,vue-server-renderer

Here is my Code:
view.js

const path = require('path')

// const VueSSR = require('vue-ssr')
const VueRender = require('../vue-ssr/renderer')

const serverConfig = require('../webpack.server')

const clientManifest = require('../../public/vue-ssr-client-manifest.json')

const indexRenderer = new VueRender({
    // This will produce main.build.js
    projectName: 'main', 
    // Cache View
    rendererOptions: {
        cache: require('lru-cache')({
            max: 10240,
            maxAge: 1000 * 60 * 15
        }),
        //! https://ssr.vuejs.org/en/bundle-renderer.html
        //! We can add here Options such as
        clientManifest
    }, 
    // Server Config 
    webpackServer: serverConfig,
    contextHandler: function (req) {
        // We Inject here the full url from laravel and data var
        let context = { url: req.query.path };
        context = Object.assign({}, context, JSON.parse(req.query.renderLaravelData));
        return context
    }
})
function main (req, res) {
    indexRenderer.render(req, res, JSON.parse(req.query.renderLaravelTemplate))
}

module.exports = {
    main
}

renderer.js

//! https://ssr.vuejs.org/en/streaming.html
const fs = require('fs')
const path = require('path')
const serialize = require('serialize-javascript')

process.env.VUE_ENV = 'server'

const NODE_ENV = process.env.NODE_ENV || 'production'
const isDev = NODE_ENV === 'development'
const { createBundleRenderer } = require('vue-server-renderer')

//! Can be Override at view.js
const DEFAULT_RENDERER_OPTIONS  = {
    cache: require('lru-cache')({
        max: 1000,
        maxAge: 1000 * 60 * 15
    })
}
/**This will Yield HTML on resources/assets/views/server/app.blade.php
 * This is the following Json Encoded Object we can Pass in Laravel Blade Template
 * That will Replace resources/assets/views/app.blade.php
 */ 
const DEFAULT_APP_HTML = '{{ APP }}'
const DEFAULT_TITLE_HTML = '{{ _VueSSR_Title }}'
const DEFAULT_KEYWORDS_HTML = '{{ _VueSSR_Keywords }}'
const DEFAULT_DESCRIPTION_HTML = '{{ _VueSSR_Description }}'

const DEFAULT_HEAD_DATA = {
    baseTitle: 'default title',
    baseKeywords: 'keywords',
    baseDescription: 'default description',
    title: '',
    description: '',
    keywords: ''
}

function getFileName (webpackServer, projectName) {
    return webpackServer.output.filename.replace('[name]', projectName)
}

class VueSSR {
    constructor ({ projectName, rendererOptions, webpackServer , AppHtml, contextHandler, defaultHeadData }) {
        this.projectName = projectName
        this.rendererOptions = Object.assign({}, DEFAULT_RENDERER_OPTIONS, rendererOptions)
        this.webpackServerConfig = webpackServer
        this.AppHtml = AppHtml || DEFAULT_APP_HTML
        this.contextHandler = contextHandler
        this.HTML = null
        this.template = ''
        this.defaultHeadData = defaultHeadData || DEFAULT_HEAD_DATA
        this.initRenderer()
    }
    //! If We Passed An Array, with headData['title','keywords','description'] var it will be replace
    headDataInject (context, html) {
        if (!context.headData) context.headData = {}
        let head
        head = html.replace('{{ _VueSSR_Title }}', (context.headData.title || this.defaultHeadData.title) + this.defaultHeadData.baseTitle)
        head = head.replace('{{ _VueSSR_Keywords }}', (context.headData.keywords || this.defaultHeadData.keywords) + this.defaultHeadData.baseKeywords)
        head = head.replace('{{ _VueSSR_Description }}', (context.headData.description || this.defaultHeadData.description) + this.defaultHeadData.baseDescription)
        return head
    }

    createRenderer (bundle) {
        //! Use the Vue SSR Function createBundleRenderer
        return createBundleRenderer(bundle, this.rendererOptions)
    }
    //! Holds BundleRenderer Vue Object
    initRenderer () {
        //! If We Already Have Bundle Then Return
        if (this.renderer) {
            return this.renderer
        }
        //! Check if We are On Production Then We use SSR Bundle renderer
        if (!isDev) {
            // const bundlePath = path.join(this.webpackServerConfig.output.path, getFileName(this.webpackServerConfig, this.projectName))
            //! Using https://ssr.vuejs.org/en/build-config.html
            const bundlePath = path.resolve(__dirname, '../vue-ssr-bundle.json')
            this.renderer = this.createRenderer(fs.readFileSync(bundlePath, 'utf-8'))
        } else {
            //! if No Node Server is Running We Use The Default Non SSR Vue Template
            require('./bundle-loader')(this.webpackServerConfig, this.projectName, bundle => {
                this.renderer = this.createRenderer(bundle)
            })
        }
    }

    parseHTML (template) {
        const i = template.indexOf(this.AppHtml)
        this.HTML = {
            head: template.slice(0, i),
            tail: template.slice(i + this.AppHtml.length)
        }
    }

    render (req, res, template) {
        if (this.template !== template) {
            this.parseHTML(template)
        }

        if (!this.renderer) {
            return res.end('waiting for compilation... refresh in a moment.')
        }

        let context = { url: req.url}

        if (this.contextHandler) {
            context = this.contextHandler(req)
        }

        this.RenderToStream(context, res)
    }

    RenderToStream (context, res) {
        //! Use RenderToStream function in Vue SSR
        const renderStream = this.renderer.renderToStream(context)
        let firstChunk = true
        
        renderStream.on('data', chunk => {
            if (firstChunk) {
                res.write(this.headDataInject(context, this.HTML.head))
                if (context.initialState) {
                    let contextClean = Object.assign({}, context.initialState);
                    delete contextClean['_registeredComponents'];
                    delete contextClean['_styles'];

                    res.write(
                        `<script>window.__INITIAL_STATE__=${
                            serialize(contextClean, { isJSON: true })
                        }</script>`
                    )
                }
                firstChunk = false
            }
            res.write(chunk)
        })

        renderStream.on('end', () => {
            res.end(this.HTML.tail)
        })
        //! If there is an Error Redirect to Error Page...
        renderStream.on('error', err => {
            console.error(err)
            res.end('<script>location.href="/"</script>')
        })
    }
}

module.exports = VueSSR

webpack.server.js

const path = require('path')
const webpack = require('webpack')
const VueSSRServerPlugin = require('vue-ssr-webpack-plugin')

//! https://ssr.vuejs.org/en/build-config.html
module.exports = {
    target: 'node',
    devtool: 'source-map',
    entry: './resources/assets/js/server-entry.js',
    output: {
        filename: '[name].build.js',
        libraryTarget: 'commonjs2',
        path: path.resolve(__dirname, '../render_server')
    },
    context: path.resolve(__dirname, '../'),
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '~': path.resolve(__dirname, '../resources/assets/js'),
            Components: path.resolve(__dirname, '../resources/assets/js/components'),
            Views: path.resolve(__dirname, '../resources/assets/js/views'),
            Routes: path.resolve(__dirname, '../resources/assets/js/routes'),
            Helpers: path.resolve(__dirname, '../resources/assets/js/helpers'),
            Plugins: path.resolve(__dirname, '../resources/assets/js/plugins'),
            Layouts: path.resolve(__dirname, '../resources/assets/js/layouts'),
            Partials: path.resolve(__dirname, '../resources/assets/js/partials'),
            Mixins: path.resolve(__dirname, '../resources/assets/js/mixins'),
            Api: path.resolve(__dirname, '../resources/assets/js/api')
        }
    },
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader',
                options: {
                    loaders:  {
                        scss: 'vue-style-loader!css-loader!sass-loader'
                    }
                }
            }, 
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }, 
            {
                test: /\.(png|jpg|gif|svg|ttf|woff|eot)$/,
                loader: 'file-loader',
                query: {
                    name: 'file/[name].[ext]'
                }
            },
            {
                test: /\.styl$/,
                loader: ['style-loader', 'css-loader', 'stylus-loader']
            }
        ]
    },
    externals: Object.keys(require('../package.json').dependencies),
    plugins: [
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
            'process.env.VUE_ENV': '"server"',
            'process.BROWSER': false
        }),
        new VueSSRServerPlugin()
    ]
}

webpack.mix.js

let { mix } = require('laravel-mix');
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */
mix.webpackConfig({
    module: {
        rules: [
            //! Allow Us To Compile Stylus
            {
                test: /\.styl$/,
                loader: ['style-loader', 'css-loader', 'stylus-loader']
            }
        ]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js',
            '~': path.resolve(__dirname, 'resources/assets/js'),
            Components: path.resolve(__dirname, 'resources/assets/js/components'),
            Views: path.resolve(__dirname, 'resources/assets/js/views'),
            Routes: path.resolve(__dirname, 'resources/assets/js/routes'),
            Helpers: path.resolve(__dirname, 'resources/assets/js/helpers'),
            Plugins: path.resolve(__dirname, 'resources/assets/js/plugins'),
            Layouts: path.resolve(__dirname, 'resources/assets/js/layouts'),
            Partials: path.resolve(__dirname, 'resources/assets/js/partials'),
            Mixins: path.resolve(__dirname, 'resources/assets/js/mixins'),
            Api: path.resolve(__dirname, 'resources/assets/js/api')
        }
    },
    plugins: [
        // This plugins generates `vue-ssr-client-manifest.json` in the
        // output directory.
        new VueSSRClientPlugin()
    ]
});

mix.js('resources/assets/js/client-entry.js', 'public/js/main.js')

it did produce me vue-ssr-bundle.json
main.build.js
and vue-ssr-client.manifest.json

Without node run:server running, if it is not running
i get
image

If i do run node run:server i get this

image

Where Can i Declare All my Meta Tags?

Hope You Can Add A guide Where To Put Meta Tags
ive check the code renderer.js

const DEFAULT_APP_HTML = '{{ APP }}'
const DEFAULT_TITLE_HTML = '{{ _VueSSR_Title }}'
const DEFAULT_KEYWORDS_HTML = '{{ _VueSSR_Keywords }}'
const DEFAULT_DESCRIPTION_HTML = '{{ _VueSSR_Description }}'

const DEFAULT_HEAD_DATA = {
    baseTitle: 'VueSSR',
    baseKeywords: ',VueSSR',
    baseDescription: 'VueSSR',
    title: '',
    description: '',
    keywords: ''
}

On the resources/assets/views/server/app.blade.php
I tried adding the following

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>@{{ _VueSSR_Title }}</title>
        @{{ _VueSSR_Keywords }}
        @{{ _VueSSR_Description }}
    </head>
    <body>
        @{{ APP }}

        <script src="{{ mix('/js/main.js') }}"></script>
    </body>
</html>

but yield same result...

on the HTML , the title stay the same , meta tags all same...

Do i need to Use Vue Head or Vue Meta here to Achieve this?
Im kinda clueless how you implement SEO here ... Looking forward for you reply

can you upgrade this to use laravel 5.5 and latest vue , vuex , vue router ?

I tried upgrading this to laravel 5.5 and latest vue , vue-router and vuex but , sadly i kept getting an error

content.js:218 Uncaught TypeError: Cannot read property 'childNodes' of null
    at HTMLDocument.ready (content.js:218)
ready @ content.js:218
global-shortcut.js:10 Uncaught TypeError: Cannot read property 'hasAttribute' of null
    at global-shortcut.js:10
    at global-shortcut.js:10

Issue In SSR

After building everything successfully, I started the render_server and reloaded my site and I get following error:
ReferenceError: window is not defined at Object.<anonymous> (__vue_ssr_bundle__:16465:1) at __webpack_require__ (__vue_ssr_bundle__:21:30) at Object.<anonymous> (__vue_ssr_bundle__:14206:1) at __webpack_require__ (__vue_ssr_bundle__:21:30) at Object.module.exports.Object.defineProperty.value (__vue_ssr_bundle__:14134:12) at __webpack_require__ (__vue_ssr_bundle__:21:30) at __vue_ssr_bundle__:64:18 at Object.<anonymous> (__vue_ssr_bundle__:67:10) at evaluateModule (D:\wamp64\www\beezoy-ssr\node_modules\vue-server-renderer\build.js:8338:21) at D:\wamp64\www\beezoy-ssr\node_modules\vue-server-renderer\build.js:8374:17

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.