如何使用PHP进行SSR Vue


这是PHP Advent Calendar 2018的第16天文章。

背景

PHP通常被称为模板引擎,但是如今有各种模板引擎,例如Twig,Smarty和胡须。

但是,在最近的Web领域中,按功能作为组件来分离职责的范式正在从HTM,CSS和JavaScript的权力分离中转移出来。
作为支持该技术的技术,已经建立了一种在称为"服务器端渲染"的服务器上执行JavaScript并建立响应的方法。
我觉得今年NuxtJS的反应特别大。

好吧,即使您想进行SSR,在引入Nuxt时也必须克服一些障碍,我觉得这有点困难。
我希望至少可以像模板引擎一样在PHP中使用Vue ...是吗?

本文将告诉您如何为此使用PHP尝试SSR Vue。

在PHP

中执行JavaScript

当前有两种在PHP中执行JavaScript的方法。

  • V8J
  • phpExecJs

在PHP V8Js中的SSR文章中详细解释了这些内容。
另外,如果您使用名为Baracoa的库,那么使用React的SSR似乎很容易,因此,如果您有兴趣,请阅读它。

这次我将使用V8Js渲染Vue。

如何使用Vue

进行SSR

Vue 2.x中的服务器端渲染在《 Vue SSR指南-在非Node.js环境中使用》中进行了描述。

render.php

1
2
3
4
5
6
7
8
9
10
11
<?php
$vue_source = file_get_contents('/path/to/vue.js');
$renderer_source = file_get_contents('/path/to/vue-server-renderer/basic.js');
$app_source = file_get_contents('/path/to/app.js');

$v8 = new V8Js();

$v8->executeString('var process = { env: { VUE_ENV: "server", NODE_ENV: "production" }}; this.global = { process: process };');
$v8->executeString($vue_source);
$v8->executeString($renderer_source);
$v8->executeString($app_source);

app.js

1
2
3
4
5
6
7
8
9
10
11
12
// app.js
var vm = new Vue({
  template: `<div>{{ msg }}</div>`,
  data: {
    msg: 'hello'
  }
})

// `vue-server-renderer/basic.js` によってエクスポーズ
renderVueComponentToString(vm, (err, res) => {
  print(res)
})

评论

  • 从文件中以文本形式读取vue.js vue-server-renderer/basic.js app.js

  • 创建V8的实例
  • 创建一个process对象并在V8中执行JavaScript代码以将process对象存储在全局空间中。

  • vue vue-server-renderer app的顺序在V8上运行

  • 它比React更复杂。

    vue服务器渲染器

    vue-server-renderer是用于SSRing Vue的实用程序。您可以在服务器环境上运行Vue并将其输出为HTML文本。

    app.js上使用的

    renderVueComponentToString是此vue-server-renderer生成的函数,并且在V8中执行此过程时,生成的结果将包含在回调中。由于res的值是由V8的print输出的,因此,是写出PHP端生成的HTML的尺寸。

    尝试实施

    V8准备

    我认为这是最艰巨的任务...
    如今,这很方便,我使用Docker是因为有一个可以使用V8的映像。

    V8Js docker映像

    目录结构

    这次我做到了。
    我在app.php中输出HTML,并将复杂的渲染过程推到另一个文件中。
    关于视图,它与webpack捆绑在一起,但是VueCLI等并未用于轻松显示结构。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    +- src
    | +- Renderer.php
    +- views/
    | +- dist/
    | +- src/
    | | +- App.vue
    | | +- index.js
    | +- package.json
    | +- webpack.config.js
    +- app.php
    +- composer.json
    • app.php-返回HTML的入口点

    • src/Renderer.php-在V8上执行JavaScript并返回结果的人

    • views/src/index.js --renderVueComponentToString入口点

    • views/src/App.vue --Vue文件

    在PHP端实现

    创建一个调用Renderer的别名。

    composer.json

    1
    2
    3
    4
    5
    6
    7
    {
      "autoload": {
          "psr-4": {
              "VueSSR\": "src/"
          }
      }
    }

    渲染器将初始化node_modules的路径。
    调用render方法时,可以将要传递给入口点和Vue的数据结构指定为数组。
    render中传递的$data作为称为__PRELOAD_STATE__的全局对象被插入到JavaScript中。

    src / Renderer.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <?php
    namespace VueSSR;
    use V8Js;
    class Renderer
    {
        private $nodePath;
        private $vueSource;
        private $rendererSource;
        private $v8;
        /**
         * @param string $nodeModulesPath
         * @return void
         */
        public function __construct (string $nodeModulesPath)
        {
            $this->nodePath = $nodeModulesPath;
            $this->v8 = new V8Js();
        }
        /**
         * @param string $entrypoint
         */
        public function render(string $entrypoint, array $data)
        {
            $state = json_encode($data);
            $app = file_get_contents($entrypoint);
            $this->setupVueRendderer();
            $this->v8->executeString("var __PRELOAD_STATE__ = ${state}; this.global.__PRELOAD_STATE__ = __PRELOAD_STATE__;");
            $this->v8->executeString($app);
        }
        private function setupVueRendderer()
        {
            $prepareCode = <<<'EOT'
    var process = {
        env: {
            VUE_ENV: "server",
            NODE_ENV: "production"
        }
    };
    this.global = { process: process };
    EOT;
            $vueSource = file_get_contents($this->nodePath . 'vue/dist/vue.js');
            $rendererSource = file_get_contents($this->nodePath . 'vue-server-renderer/basic.js');
            $this->v8->executeString($prepareCode);
            $this->v8->executeString($vueSource);
            $this->v8->executeString($rendererSource);
        }
    }

    准备HTML输出。
    您可以通过执行以下操作将其用作部分。

    app.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?php
    require_once "vendor/autoload.php";
    use VueSSR\Renderer;
    $renderer = new Renderer('views/node_modules/');
    ?>
    <!doctype html>
    <html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        Example SSR Vue on PHP
    </head>
    <body>
    <?php $renderer->render('./views/dist/main.bundle.js', [
        'message' => 'hello vue!'
    ]); ?>
    </body>
    </html>

    查看实施

    建立依赖关系。

    1
    2
    npm install --save vue vue-server-renderer
    npm install -D webpack webpack-cli vue-loader vue-template-compiler

    使用WebPack设置构建。这次只有一个入口点,但是可以将多个入口点设置到单独的页面。
    现在将其输出到views/dist/main.bundle.js

    视图/ webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const path = require('path')
    const VueLoaderPlugin = require('vue-loader/lib/plugin')

    module.exports = {
      entry: './src',
      output: {
        path: path.resolve('./dist'),
        filename: '[name].bundle.js',
      },
      module: {
        rules: [
          { test: /\.vue$/, use: 'vue-loader' },
        ]
      },
      plugins: [
        new VueLoaderPlugin(),
      ]
    }

    它读取

    App.vue并将其传递给renderVueComponentToString
    此时,从服务器接收到__PRELOAD_STATE__并将其传递给App.propsData
    这使您可以查看通过props从服务器从App.vue传递的值。

    视图/ src / index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import App from './App.vue'

    App.propsData = __PRELOAD_STATE__
    var vm = new Vue(App);

    renderVueComponentToString(vm, (err, res) => {
        if (err) throw new Error(err);
        print(res)
    })

    是的,这是Vue文件。
    如您所见,您可以使用props将值从服务器传递到message

    views / src / App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <template>
      <p>{{message}}</p>
    </template>

    <script>
    export default {
      props: {
        message: { type: String }
      }
    }
    </script>

    概要

    我介绍了如何使用PHP进行SSR VueJS。
    目前这并不容易,但是如果您使用PHP并想尝试Vue,则可能会发现一些新东西。

    这次引入的代码发布在名为example-php-ssr-vue的存储库中,因此,如果您有兴趣,请阅读它。