exodusanto / laravel-ssr

Vue Server Side Rendering with Laravel + Node
19 stars 3 forks source link

have u successfully use VueSSRClientPlugin before? #3

Closed codeitlikemiley closed 6 years ago

codeitlikemiley commented 6 years ago

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

codeitlikemiley commented 6 years ago

ive got it working , only the rendere.js has cause me this error before i got this

const bundlePath = path.resolve(__dirname, '../vue-ssr-bundle.json')

now

if (!isDev) {
            const bundlePath = path.join(this.webpackServerConfig.output.path, getFileName(this.webpackServerConfig, this.projectName))
            //! Using https://ssr.vuejs.org/en/build-config.html
            this.renderer = this.createRenderer(fs.readFileSync(bundlePath, 'utf-8'))

and for the record i remove this in

new webpack.optimize.CommonsChunkPlugin({
            name: 'manifest',
            minChunks: Infinity
        }),

webpack.mix.json coz its giving an error of

main.js:1 Uncaught ReferenceError: webpackJsonp is not defined
    at main.js:1

it seems to me that we dont need this part since we are using laravel mix manifest ...

i have critical css inject as a result :)

image

image