服务端渲染简单介绍和施行金沙js8331,js_脚本之家

前言

什么是 ssr

ssr , Server Side Render的简称, 服务端渲染. 首先服务端渲染并不神秘, 在
ajax 兴起之前, 所有 web 应用都是服务端渲染, 服务器直接返回 html
文本给浏览器, 用户操作比如在 A 页面注册提交表单, 跳转到B 页面,
服务器需要返回两个页面. 这样的弊端显而易见, 加大了服务器的消耗, 随着
JavaScript 的发展, ajax 技术的出现,
客户端的操作通过请求接口的形式与服务器交互, 服务器不用返回整个页面,
而只是数据. 后来出现了后端模版, 比如 jsp, cshtml

<table>
    <c:forEach var="data" items="${datas}" varStatus="loop">
    <tr>
        <td>${loop.index + 1}</td>
        <td>${data.time}</td>
        <td>${data.msg}</td>
    </tr>
    </c:forEach>
</table>

用户在首次进入页面的时候, 通过服务端渲染给出 html, 用户操作使用 ajax
与服务端交互, 动静混合的形式.

后来随着 JavaScript 的发展, 前端模版和近年SPA 框架的发展,
呈现页面完全静态化, 动态内容交给前端(Javscript), 服务器只提供数据(一般以
json 的形式). 用户看到页面, 大致上需要如下过程(忽略 cdn 等)

1.浏览器加载所有静态资源(html,css,js等)–> 2.js 发起请求获取数据
–> 3.渲染页面 –> 呈现用户

好处是前后端完全分离(开发部署), 各司其职,
同时也节约服务器资源(只有数据交互).
此时用户所获取的 html 只是如下的片段

<!DOCTYPE html><html class=has-full>
<head><meta charset=utf-8>
<title>人事管理系统</title>
<link href=/static/css/app.0c7e1e58d27be30db979adc44f7cd4eb.css rel=stylesheet>
</head>
<body><div id=app></div>
<script type=text/javascript src=/static/js/manifest.ca2797291add890279b8.js></script>
<script type=text/javascript src=/static/js/vendor.ee32e29412ede428634a.js></script>
</body>
</html>

其中2, 3 步骤是最耗费时间的, 因为获取数据受到用户网络,
服务器带宽等条件的显示, 而且可以通过业务数据再次加载一些静态资源.
随着业务的复杂, 打包处理 bundle 逐渐增大, 用户看到页面的时间(首屏),
即内容到达时间(time-to-content)将延长, 降低用户体验,
对电商网站流量转换率影响比较明显.

如果我们需要对现有的vue项目进行ssr改造,使用nuxt.js是一个不错的选择。这里用作例子的“现有项目”是一个“高仿”饿了么外卖APP的spa。不过我没有把全部功能开发出来,只是做出几个基本界面意思意思

ssr 所做的事情

借用 react ssr 的两张图说明问题( vue 的 ssr 和 react 同理)

ssr:

金沙js8331 1

image

csr:

金沙js8331 2

image

最大的差异是, 服务端直接返回的 渲染完毕html 页面, 获取业务数据,
填充业务组件都在服务端完成, 用户能够更快的看到页面内容,
同时也有利于爬虫抓取(SEO).

但是 ssr 也不是万能的, 需要 node 服务器, 很耗费性能, 需要做好缓存和优化,
相当于空间换时间. 全站 ssr 明显不可取, 现在流行较多的是 首屏 ssr ,甚至
首屏部分 ssr

参考资料

前后端渲染之争

Vue 全站服务器渲染 SSR
实践

下面就以这个demo为小白鼠进行ssr改造

Nuxt

有上述可知, ssr 应该有两个代码入口, 服务端和客户端, 通过 webpack
打包之后为别为 server-bundle 和 client-bundle, 页面第一次呈现, 通过
server-bundle , 获取业务数据, 填充数据, 渲染组件, 发送 html 给浏览器,
之后用户操作通过 client-bundle, 依旧是在浏览器范围内.

从零开始配置vue ssr 是比较困难的, 幸好有 nuxt
api.

nuxt预设了 vue 服务端渲染的一些配置, 约定大于配置,
pages 路由, vuex 模块划分

准备

Quick start

vue init nuxt-community/starter-template my-project

# 安装依赖
yarn install
# 开发模式运行
yarn run dev
# build 生成环境
yarn run build
# 运行已 build 的代码
yarn run start

目录结构
layouts, middleware, pages, static, store 目录必须存在

配置文件nuxt.config.js
link,
尽可能罗列了 nuxt.config.js可配置项和默认值

module.exports = {
  cache: {},
  css: [
    // 加载一个 node.js 模块
    //  'hover.css/css/hover-min.css',
    //  // 同样加载一个 node.js 模块,不过我们定义所需的预处理器
    //  { src: 'bulma', lang: 'sass' },
    //  // 项目中的 CSS 文件
    //  '~assets/css/main.css',
    //  // 项目中的 Sass 文件
    //  { src: '~assets/css/main.scss', lang: 'scss' } // 指定 scss 而非 sass
  ],

  // 默认 true
  dev: process.env.NODE_ENV !== 'production',

  // 创建环境变量
  env: {},

  // 配置 Nuxt.js 应用生成静态站点的具体方式。
  genetate: {
    dir: '',
    minify: '',
    routes: [],
  },


  /*
    * vue-meta
    * Headers of the page
    */
  head: {
    title: 'ssr-vue',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'Nuxt.js project' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  /*
  ** Customize the progress bar color
  */
  loading: { color: '#3B8070' },
  /*
  ** Build configuration
  */
  build: {
    /*
    ** Run ESLint on save
    */
    extend (config, { isDev, isClient }) {
      if (isDev && isClient) {
        config.module.rules.push({
          enforce: 'pre',
          test: /.(js|vue)$/,
          loader: 'eslint-loader',
          exclude: /(node_modules)/
        })
      }
    }
  },

  performance: {
    gzip: false,
    prefetch: true
  },

  // 引入 Vue.use 的插件
  plugins: [],

  // 默认当前路径
  rootDir: process.cwd(),

  router: {
    base: '',
    mode: 'history',
    linkActiveClass: 'nuxt-link-active',
    scrollBehavior: (to, from, savedPosition) => {
      // savedPosition 只有在 popstate 导航(如按浏览器的返回按钮)时可以获取。
      if (savedPosition) {
        return savedPosition
      } else {
        let position = {}
        // 目标页面子组件少于两个
        if (to.matched.length < 2) {
          // 滚动至页面顶部
          position = { x: 0, y: 0 }
        }
        else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
          // 如果目标页面子组件中存在配置了scrollToTop为true
          position = { x: 0, y: 0 }
        }
        // 如果目标页面的url有锚点,  则滚动至锚点所在的位置
        if (to.hash) {
          position = { selector: to.hash }
        }
        return position
      }
    },
    // default
    middleware: 'user-agent',
    // 扩展路由
    extendRoutes: () => {},

    // 默认同 rootDir
    srcDir: this.rootDir,

    transition: {
      name: 'page',
      mode: 'out-in'
    },
    watchers: {
      chokidar: {}, // 文件监控
      webpack: {
        aggregateTimeout: 300,
        poll: 1000
      }
    }
  }
}

现有目录

pages 路由

路由,
约定大于配置, 支持动态, 嵌套, 动态嵌套路由, 过渡效果和中间件,
服务端渲染简单介绍和施行金沙js8331,js_脚本之家。通过文件夹目录名称, 组件名称, 生成路由配置,
默认的 transitionName 为 page, 可在 assets 中添加全局的过渡效果

路由中间件:

在匹配页面之前执行;
nuxt.config.js –> 执行middleware –> 匹配布局 –> 匹配页面
路由中间件

很明显,这是使用vue-cli搭建的项目

视图

模版

默认的 html 模版: 应用根目录下的 app.html 文件, 没有改文件,
则采用默认的模版

<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
  </body>
</html>

页面

页面就是我们最熟悉的.vue文件, 单文件组件, 但是 nuxt 有一些不同的地方,
混入了 asyncData, fetch, head 三个方法, 还有 指定
layout, transition, scrollToTop, validate, middleware配置项

asyncData 和 fetch都是获取数据的方法, 不同的是,
asyncData是请求接口的数据, fetch 是用于填充store 数据,
不会设置组件的数据, 两者都在页面加载之前调用,

代码build 之后有服务端和客户端两个入口, build 之后对应为 client.js 和
server.js,
asyncData,和 fetch 第一次在服务端执行, 第二次切换页面后在浏览器执行

head 方法相关使用方法, 可参考 vue-meta
nuxt.config.js 默认定义了全局的 mota 标签

页面相关
API

异步数据

通过 asyncData 获取异步数据, 第一个参数为上下文对象 context, 推荐使用
promise 或者 async/await

asyncData
context
对象

其中prod.server.js是build之后的启动文件

资源文件

项目中编写 js 文件和普通项目一样. 通过 webpack 处理, 对于一些不需要
webpack loader 处理的静态资源文件,
必须放在项目根目录下的static文件夹中, 项目中直接使用/引用相关资源,

需要 webpack 处理的 静态文件,
可以覆盖 nuxt.config.js 中build 字段中 loaders 中 url-loader 或者
file-loader的 默认配置, 进行自定义设置

loaders
配置

[
  {
    test: /.(png|jpe?g|gif|svg)$/,
    loader: 'url-loader',
    query: {
      limit: 1000, // 1KO
      name: 'img/[name].[hash:7].[ext]'
    }
  },
  {
    test: /.(woff2?|eot|ttf|otf)(?.*)?$/,
    loader: 'url-loader',
    query: {
      limit: 1000, // 1 KO
      name: 'fonts/[name].[hash:7].[ext]'
    }
  }
]

dataa.json文件是模拟数据,在build/dev-server.js中会用到它

npm 模块 & 插件

服务端渲染, node 直接返回 html 给客户端, 所以 npm模块和插件应在 整个应用
实例化之前运行, 并且必须支持 ssr(服务端获取不到 window 等对象),
组件的生命周期只有beforeCreatecreated在client 和 server 均调用.
其余钩子函数只在 client 调用.

node_modules 中安装的模块在组件中可以直接使用

import someModules from 'some-module'

避免多个组件引用同一个模块重复打包的问题, 需要在 nuxt.config.js 中配置
vendor(路径和 plugin一致), 尽量将第三方模块打包至单独的文件中去

module.exports = {
    build: {
        vendor: ['path/to/your/modules'],
    },
    plugins: ['path/to/your/modules']
}

可以区分server 端插件和 client 端plugin, ssr 为 true 则只在服务端使用,
为 false 则反之

 plugins: [{src: 'path/to/your/modules', ssr: false}]

安装与配置

ssr 部署及 pm2 的使用

使用 nuxt 官方模版新建的项目, 可以运行yarn build 命令进行构建, build
模板为项目根路径下的 .nuxt 文件夹, 其中 client.js 为客户端入口,
server.js 为服务端入口, 通过命令nuxt start 启动 build 之后的代码.

通过简单命令启动在生成环境并不是好办法, 因此我们需要工具, 推荐pm2;

简单来说, PM2是node编写的,
进程管理工具,可以利用它来简化很多应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。

安装之类的不在赘述, 具体参考文档
pm2

安装nuxt

简单示例

在项目根目录新建 start.sh, 内容如下

#! /bin/bash
nuxt build
nuxt start

同样在项目根目录新建pm2.config.js, 内容如下

module.exports = {
  apps: [
    {
      name: 'test',
      script: './start.sh',
      env: {
        NODE_ENV: 'development'
      },
      env_production: {
        NODE_ENV: 'production'
      }
    }
  ]
}

参数设置可参考pm2 文档, 这只是个简单示例, 正式环境需要设置集群模式等
运行

pm2 start /path/to/pm2.config.js

即可开启 ssr 服务, 正式环境需要 nginx 代理到80或者443端口

npm install --save-dev nuxt

实践遇坑小计

  1. nuxt.config.js 中 plugins 配置错误则项目在使用引入包时会运行失败,
    报错信息显示和插件使用错误一样(比如传参错误), 容易误解.
  2. 引入的组件库必须配置 plugins, 但是有的组件库不支持 ssr.
  3. 未完待续

新建nuxt文件夹

我们需要在根目录下建立一个nuxt文件夹,该文件夹的内部组织按照nuxt本身的应用目录架构进行组织

注意,在static目录下有一个logo.png,它将作为项目图标,在nuxt.config.js中这张图片的地址直接写成/logo.png。下一节会对nuxt文件夹的组织进行详细说明。

新建并设置nuxt.config.js

当然,我们也需要在根目录下创建nuxt.config.js文件用于组织Nuxt.js
应用的个性化配置,在这个项目中,需要进行设置的点有:

源码目录路径 head中的meta以及link 全局的css样式
项目中需要使用图片和字体文件,虽然nuxt默认对其进行配置,但我们需要重新定义部分内容
项目中使用了sass,需要对其进行配置

所以,nuxt.config.js的代码如下

module.exports = { // 设置nuxt源码目录路径 srcDir: "nuxt/", head: { title: "sell-nuxt", meta: [ { charset: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1,user-scalable=no,maximum-scale=1.0,minimum-scale=1.0" } ], link: [ { rel: "shortcut icon", type: "image/png", // 注意图片的路径直接指向static下的logo.png href: "/logo.png" } ] }, css: [ "~assets/reset.css" ], build: { vendor: ['axios'], loaders: [ { test: /.(png|jpe?g|gif|svg)?$/, loader: 'url-loader', options: { limit: 10000, name: 'img/[name].[hash:7].[ext]' } }, { test: /.(woff2?|eot|ttf|otf)?$/, loader: 'url-loader', options: { limit: 10000, name: 'fonts/[name].[hash:7].[ext]' } }, { test: /.scss$/, loader: "vue-style-loader!css-loader!sass-loader" } ] }}

配置package.json

package.json是npm的配置文件,现在,我们需要对script选项进行配置

"scripts": { "dev": "nuxt", "build": "nuxt build", "start": "nuxt start", "generate": "nuxt generate", "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", "precommit": "npm run lint" }

可以参考nuxt的命令列表

改造后的目录

其中有个nuxt.api.js,这个文件是为完成ssr改造后的项目提供api接口的,使用koa,运行在3001端口

nuxt文件夹组织

因为我们已经把项目代码开发出来了,所以nuxt文件夹里面的大部分代码可以直接从已有代码中拷贝

layouts

布局目录 layouts 用于组织应用的布局组件,nuxt可通过添加
layouts/default.vue 文件来扩展应用的默认布局,在layouts下新建default.vue

      import axios from 'axios'; // &#24341;&#20837;&#32452;&#20214; import header from "~/components/header/header.vue"; import tab from "~/components/tab/tab.vue"; export default { data:function(){ return { seller:{} } }, created:function(){ axios.get("http://localhost:3001/seller").then(res=&gt;{ console.log; this.seller = res.data.data; }); }, components:{ "v-header":header, "v-tab":tab } }

“ 组件用于显示页面的主体内容,即“商品”、“评论”、“商家”这几个部分

pages

nuxt依据 pages 目录结构自动生成 vue-router
模块的路由配置,这无疑是非常方便的

不难看出goods、patings、seller分别对应“商品”、“评论”、“商家”

这样子设置,则nuxt自动生成的路由配置如下

router: { routes: [ { name: 'index', path: '/', component: 'pages/index.vue' }, { name: 'goods', path: '/goods', component: 'pages/goods/index.vue' }, { name: 'patings', path: '/patings', component: 'pages/patings/index.vue' }, { name: 'seller', path: '/seller', component: 'pages/seller/index.vue' } ]}

但我们希望默认进入goods,nuxt官方文档并没有说如何设置默认路径,这就是为什么要在pages下面创建一个index.vue

 export default { created:function(){ this.$router.push; // &#39029;&#38754;&#21152;&#36733;&#26102;&#36339;&#36716; } }

对于那些不需要像在pages下的页面组件那样有 asyncData
方法的特性的组件,我们可以将他们放到components目录下

assets

资源目录 assets 用于组织未编译的静态资源如 LESS、SASS 或
JavaScript,这个目录是我感觉在引用路径时最坑的一个。
在官方文档中,每个目录都有别名,这些别名在nuxt.config.js中配置时是有效的,比如之前配置的css选项,但不代表我们在组件中使用这些别名会有效,在组件中我们最好使用相对路径,比如在components/header/header.vue中就不能直接写~assets,而得老老实实写相对路径

 @import "../../assets/mixin.scss"; @import "./header.scss";

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

发表评论

电子邮件地址不会被公开。 必填项已用*标注