vue服务端渲染(ssr)

Posted by 薛纪昌 on August 24, 2018

最近在找关于vue的seo方法,就接触到了服务端渲染这块。

关于vue ssr的基础知识我是在 这里 学习的,然后记录一下学习和部署的过程。

安装

首先要安装依赖 Vue 和 vue-server-renderer

npm install vue vue-server-renderer --save

开始

创建ssr.js

const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
server.get('*', (req, res) => {
  const app = new Vue({
    data: {
      url: req.url
    },
    template: `<div>访问的 URL 是: </div>`
  })
  renderer.renderToString(app, (err, html) => {
    if (err) {
      res.status(500).end('Internal Server Error')
      return
    }
    res.end(`
      <!DOCTYPE html>
      <html lang='en'>
        <head><title>Hello</title></head>
        <body>${html}</body>
      </html>
    `)
  })
})
server.listen(8080)

运行后,可能会响应403,我们只需更改下端口号就行了。

但是这仅仅是静态的数据,还有我们需要引入文件模板

引入模板

创建一个模板文件 blogger.template.html

<html>
  <head>
    <!-- 使用双花括号(double-mustache)进行 HTML 转义插值(HTML-escaped interpolation) -->
    <title></title>
    <!-- 使用三花括号(triple-mustache)进行 HTML 不转义插值(non-HTML-escaped interpolation) -->
  </head>
  <body>
    <div><!-- Image to hack wechat -->
<!-- <img src="/img/icon_wechat.png" width="0" height="0"> -->
<!-- <img src="/img/post-bg-desk.jpg" width="0" height="0"> -->

<!-- Post Header -->
<style type="text/css">
    header.intro-header{
        position: relative;
        background-image: url('/img/post-bg-desk.jpg')
    }

    
</style>
<header class="intro-header" >
    <div class="header-mask"></div>
    <div class="container">
        <div class="row">
            <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
                <div class="post-heading">
                    <div class="tags">
                        
                        <a class="tag" href="/tags/#gulp" title="gulp">gulp</a>
                        
                        <a class="tag" href="/tags/#笔记" title="笔记">笔记</a>
                        
                    </div>
                    <h1>gulp入门笔记</h1>
                    
                    
                    <h2 class="subheading"></h2>
                    
                    <span class="meta">Posted by 薛纪昌 on August 24, 2018</span>
                </div>
            </div>
        </div>
    </div>
</header>

<!-- Post Content -->
<article>
    <div class="container">
        <div class="row">

    <!-- Post Container -->
            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                post-container">

				<p>入门指南在这里<a href="http://www.gulpjs.com.cn/docs/getting-started/">gulp入门教程</a></p>
<h2 id="开始">开始</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i -g gulp        // 全局安装gulp
npm i gulp --save    // 在项目中安装gulp
</code></pre></div></div>
<p>在项目根目录下创建一个gulpfile.js文件,写入以下代码</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">gulp</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">gulp</span><span class="dl">'</span><span class="p">);</span>

<span class="kd">const</span> <span class="nx">input</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">lib/</span><span class="dl">'</span><span class="p">,</span>
      <span class="nx">output</span><span class="o">=</span> <span class="dl">'</span><span class="s1">build/</span><span class="dl">'</span><span class="p">;</span>

<span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">src</span><span class="p">(</span><span class="nx">input</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">js/*.js</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="nx">output</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">js</span><span class="dl">'</span><span class="p">));</span>
<span class="p">});</span>
</code></pre></div></div>
<p>gulp 会执行名称为“default”的任务</p>

<p>然后在终端输入gulp,执行完毕之后,lib/js/下的所有js文件会被写入到build/js/目录之下</p>

<p>这是gulp基本的功能,如果需要其他功能要安装gulp插件。</p>
<h2 id="插件">插件</h2>
<p>gulp提供了许多插件来实现各种功能</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i gulp-ruby-sass gulp-babel gulp-autoprefixer gulp-minify-css gulp-uglify gulp-rename gulp-notify gulp-sourcemaps del vinyl-paths babel-preset-es2015 --save
</code></pre></div></div>
<p>执行上面这条代码,安装所需插件</p>

<p>gulp-autoprefixer: 可以添加css兼容前缀</p>

<p>gulp-notify: 用来输出log信息</p>

<p>vinyl-paths: 用来获取 stream 中每个文件的路径</p>
<h2 id="配置">配置</h2>
<p>我们先来写一个可以转换es6代码并压缩的task</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">gulp</span><span class="p">.</span><span class="nx">task</span><span class="p">(</span><span class="dl">'</span><span class="s1">scripts</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">gulp</span><span class="p">.</span><span class="nx">src</span><span class="p">(</span><span class="nx">input</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">js/*.js</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">rename</span><span class="p">({</span>
      <span class="na">suffix</span><span class="p">:</span> <span class="dl">'</span><span class="s1">.min</span><span class="dl">'</span>
    <span class="p">}))</span>
    <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">babel</span><span class="p">({</span>
      <span class="na">presets</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">es2015</span><span class="dl">'</span><span class="p">]</span>
    <span class="p">}))</span>
    <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">uglify</span><span class="p">())</span>
    <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">gulp</span><span class="p">.</span><span class="nx">dest</span><span class="p">(</span><span class="nx">output</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">js</span><span class="dl">'</span><span class="p">))</span>
    <span class="p">.</span><span class="nx">pipe</span><span class="p">(</span><span class="nx">notify</span><span class="p">({</span>
      <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Scripts task complete</span><span class="dl">'</span>
    <span class="p">}));</span>
<span class="p">});</span>
</code></pre></div></div>
<p>第一个pipe:将要输入的文件名后缀添加上 .min</p>

<p>第二个pipe:转换es6代码</p>

<p>第三个pipe:压缩js代码</p>

<p>第四个pipe:将处理完毕的js代码写到输出目录</p>

<p>第五个pipe:输出log信息</p>

<hr />

<p>再写一个处理css文件的task</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gulp.task('sass', function () {
  return sass(input + 'css/*.scss')
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(rename({
      suffix: '.min'
    }))
    .pipe(minifycss())
    .pipe(gulp.dest(output + 'css/'))
    .pipe(notify({
      message: 'Sass task complete'
    }));
});
</code></pre></div></div>
<p>gulp-ruby-sass:sass()需要传入转换的scss文件路径才能执行,所以不是再使用gulp.src() 来获取目标文件了</p>

<p>第一个pipe:添加css兼容前缀</p>

<p>第二个pipe:重命名</p>

<p>第三个pipe:压缩</p>

<p>第四个pipe:写出文件</p>

<p>第五个pipe:输出log信息</p>

<p>gulp-sass:可以使用gulp.src()来获取目标文件,具体如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
return gulp.src(input + 'css/*.scss')
  .pipe(sass())
...
</code></pre></div></div>
<p>处理js和css的task写好了之后,我们需要在写出文件之前先删除之前的旧文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gulp.task('clean', function (cb) {
  return gulp.src(output)
    .pipe(vinylPaths(del));
});
</code></pre></div></div>
<p>第一个pipe:vinylPaths获取需要删除文件的路径,并传入del</p>

<p>然后修复default的task</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gulp.task('default', ['clean'], function () {
  gulp.start('sass', 'styles', 'scripts');
});
</code></pre></div></div>
<p>第二个参数为先执行的task名称,这里要注意一点,摘自gulp中文网</p>
<blockquote>
  <p>注意: 你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。</p>
</blockquote>

<p>###注意事项</p>
<ol>
  <li>在macos中,目录名的大小写是敏感的,如果不注意可能会导致watch功能的失效。
:)</li>
</ol>


                <hr style="visibility: hidden;">

                <ul class="pager">
                    
                    <li class="previous">
                        <a href="/2018/08/24/express%E5%BC%80%E5%90%AFhttps%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98/" data-toggle="tooltip" data-placement="top" title="express 开启 https服务器遇到的问题">
                        Previous<br>
                        <span>express 开启 https服务器遇到的问题</span>
                        </a>
                    </li>
                    
                    
                    <li class="next">
                        <a href="/2018/08/24/vue%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%B8%B2%E6%9F%93-ssr/" data-toggle="tooltip" data-placement="top" title="vue服务端渲染(ssr)">
                        Next<br>
                        <span>vue服务端渲染(ssr)</span>
                        </a>
                    </li>
                    
                </ul>


                <!--Gitalk评论start  -->
                
                <!-- 引入Gitalk评论插件  -->
                <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
                <script src="https://unpkg.com/gitalk@latest/dist/gitalk.min.js"></script>
                <script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script>
                <div id="gitalk-container"></div>
                <script type="text/javascript">
                    var gitalk = new Gitalk({
                    clientID: '02884ac803aa950dc407',
                    clientSecret: 'bb44d7f83924d1cf7cab3a4e930eb269269fc407',
                    repo: 'jichangee.github.io',
                    owner: 'jichangee',
                    admin: ['jichangee'],
                    distractionFreeMode: false,
                    id: md5(window.location.pathname),
                    });
                    gitalk.render('gitalk-container');
                </script>
                
                <!-- Gitalk end -->

                

            </div>  

    <!-- Side Catalog Container -->
        
            <div class="
                col-lg-2 col-lg-offset-0
                visible-lg-block
                sidebar-container
                catalog-container">
                <div class="side-catalog">
                    <hr class="hidden-sm hidden-xs">
                    <h5>
                        <a class="catalog-toggle" href="#">CATALOG</a>
                    </h5>
                    <ul class="catalog-body"></ul>
                </div>
            </div>
        

    <!-- Sidebar Container -->
            <div class="
                col-lg-8 col-lg-offset-2
                col-md-10 col-md-offset-1
                sidebar-container">

                <!-- Featured Tags -->
                
                <section>
                    <hr class="hidden-sm hidden-xs">
                    <h5><a href="/tags/">FEATURED TAGS</a></h5>
                    <div class="tags">
        				
                            
                				<a href="/tags/#centos" title="centos" rel="2">
                                    centos
                                </a>
                            
        				
                            
                				<a href="/tags/#javascript" title="javascript" rel="5">
                                    javascript
                                </a>
                            
        				
                            
                				<a href="/tags/#vue" title="vue" rel="3">
                                    vue
                                </a>
                            
        				
                            
        				
                            
        				
                            
                				<a href="/tags/#git" title="git" rel="2">
                                    git
                                </a>
                            
        				
                            
                				<a href="/tags/#笔记" title="笔记" rel="3">
                                    笔记
                                </a>
                            
        				
                            
                				<a href="/tags/#css" title="css" rel="3">
                                    css
                                </a>
                            
        				
                            
        				
                            
        				
                            
                				<a href="/tags/#chrome" title="chrome" rel="2">
                                    chrome
                                </a>
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
                            
        				
        			</div>
                </section>
                

                <!-- Friends Blog -->
                
            </div>
        </div>
    </div>
</article>






<!-- async load function -->
<script>
    function async(u, c) {
      var d = document, t = 'script',
          o = d.createElement(t),
          s = d.getElementsByTagName(t)[0];
      o.src = u;
      if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
      s.parentNode.insertBefore(o, s);
    }
</script>
<!-- anchor-js, Doc:http://bryanbraun.github.io/anchorjs/ -->
<script>
    async("//cdnjs.cloudflare.com/ajax/libs/anchor-js/1.1.1/anchor.min.js",function(){
        // BY Fix:去除标题前的‘#’ issues:<https://github.com/qiubaiying/qiubaiying.github.io/issues/137>
        // anchors.options = {
        //   visible: 'always',
        //   placement: 'right',
        //   icon: '#'
        // };
        anchors.add().remove('.intro-header h1').remove('.subheading').remove('.sidebar-container h5');
    })
</script>
<style>
    /* place left on bigger screen */
    @media all and (min-width: 800px) {
        .anchorjs-link{
            position: absolute;
            left: -0.75em;
            font-size: 1.1em;
            margin-top : -0.1em;
        }
    }
</style>
</div>
  </body>
</html>

这里直接使用gitbook中的例子,稍作修改

修改ssr.js代码

const Vue = require('vue')
const server = require('express')()
const renderer = require('vue-server-renderer').createRenderer()
const fs = require('fs');
const template = fs.readFileSync('./blogger.template.html', 'utf-8')
const conn = require('./mysql')

const app = new Vue({
  data: {
    listTime: [],
    d: {
      content: '',
      title: ''
    }
  },
  template: template
})

server.get('/blogger.html', (req, res) => {
  var id = req.query.id // 获取get参数
  var WHERE = ' WHERE id=' + id
  var sql = 'SELECT * FROM blogger' + WHERE
  conn.query(sql, function (error, result) {
    if (result.length > 0) {
      var data = result[0]
      app.d.title = data.title
      app.d.content = data.content
      renderer.renderToString(app, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error')
          return
        }
        res.end(html)
      })
    } else {
      res.status(404).end('404')
    }
  })
})
server.listen(3101)
console.log('http running at 3101...');

部署

将ssr.js部署到服务器上需要用到nginx反向代理