构建同构渲染

构建流程

源码结构
我们需要使用 webpack 来打包我们的 Vue 应用程序。事实上,我们可能需要在服务器上使用
webpack 打包 Vue 应用程序,因为:
  • 通常 Vue 应用程序是由 webpack 和 vue-loader 构建,并且许多 webpack 特定功能不能直接在Node.js 中运行(例如通过 file-loader 导入文件,通过 css-loader 导入 CSS)。
  • 尽管 Node.js 最新版本能够完全支持 ES2015 特性,我们还是需要转译客户端代码以适应老版浏览器。这也会涉及到构建步骤。
所以基本看法是,对于客户端应用程序和服务器应用程序,我们都要使用 webpack 打包 - 服务器需要「服务器 bundle」然后用于服务器端渲染(SSR),而「客户端 bundle」会发送给浏览器,用于混合静态标记。
现在我们正在使用 webpack 来处理服务器和客户端的应用程序,大部分源码可以使用通用方式编写,可以使用 webpack 支持的所有功能。同时,在编写通用代码时,有一些事项要牢记在心。
一个基本项目可能像是这样:

App.vue
<template>
<!-- 客户端渲染的入口节点 -->
<div id="app"><h1>拉勾教育</h1>
</div>
</template> <script>
export default { name: 'App'
}
</script> <style> </style>
app.js
app.js 是我们应用程序的「通用 entry」。在纯客户端应用程序中,我们将在此文件中创建根 Vue 实 例,并直接挂载到 DOM。但是,对于服务器端渲染(SSR),责任转移到纯客户端 entry 文件。 app.js 简单地使用 export 导出一个 createApp 函数:
import Vue from 'vue'
import App from './App.vue' // 导出一个工厂函数,用于创建新的
// 应用程序、router 和 store 实例
export function createApp () { const app = new Vue({ // 根实例简单的渲染应用程序组件。 render: h => h(App) })return { app }
}
entry-client.js
客户端 entry 只需创建应用程序,并且将其挂载到 DOM 中:
import { createApp } from './app' // 客户端特定引导逻辑……
const { app } = createApp() // 这里假定 App.vue 模板中根元素具有 `id="app"`
app.$mount('#app')
entry-server.js
服务器 entry 使用 default export 导出函数,并在每次渲染中重复调用此函数。此时,除了创建和返回应用程序实例之外,它不会做太多事情 - 但是稍后我们将在此执行服务器端路由匹配 (server-side route matching) 和数据预取逻辑 (data pre-fetching logic)。
import { createApp } from './app' export default context => { const { app } = createApp() return app
}

构建配置

安装生产依赖
npm i vue vue-server-renderer express cross-env

安装开发依赖
npm i -D webpack webpack-cli webpack-merge
webpack-node-externals @babel/core
@babel/plugin-transform-runtime
@babel/preset-env babel-loader
css-loader url- loader file-loader
rimraf vue-loader
vue-template-compiler
friendly-errors- webpack-plugin

配置文件及打包命令
(1)初始化 webpack 打包配置文件

webpack.base.config.js

/*** 公共配置 */
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const path = require('path')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const resolve = file => path.resolve(__dirname, file)
const isProd = process.env.NODE_ENV === 'production'module.exports = {mode: isProd ? 'production' : 'development',output: {path: resolve('../dist/'),publicPath: '/dist/',filename: '[name].[chunkhash].js'},resolve: {alias: {// 路径别名,@ 指向 src '@': resolve('../src/')},// 可以省略的扩展名 // 当省略扩展名的时候,按照从前往后的顺序依次解析extensions: ['.js', '.vue', '.json']},devtool: isProd ? 'source-map' : 'cheap-module-eval-source-map',module: {rules: [// 处理图片资源 {test: /\.(png|jpg|gif)$/i,use: [{loader: 'url-loader',options: {limit: 8192,},},],},// 处理字体资源 {test: /\.(woff|woff2|eot|ttf|otf)$/,use: ['file-loader',],},// 处理 .vue 资源 {test: /\.vue$/,loader: 'vue-loader'},// 处理 CSS 资源 // 它会应用到普通的 `.css` 文件 // 以及 `.vue` 文件中的 `<style>` 块 {test: /\.css$/,use: ['vue-style-loader', 'css-loader']},// CSS 预处理器,参考:https://vue-loader.vuejs.org/zh/guide/pre- processors.html // 例如处理 Less 资源 {test: /\.less$/,use: ['vue-style-loader', 'css-loader', 'less-loader']},]},plugins: [new VueLoaderPlugin(),new FriendlyErrorsWebpackPlugin()]
}
webpack.client.config.js
/*** 客户端打包配置 */
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
const VueSSRClientPlugin = require('vue-server-renderer/client-plugin')module.exports = merge(baseConfig, {entry: {app: './src/entry-client.js'},module: {rules: [// ES6 转 ES5 {test: /\.m?js$/,exclude: /(node_modules|bower_components)/,use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env'],cacheDirectory: true,plugins: ['@babel/plugin-transform-runtime']}}},]},// 重要信息:这将 webpack 运行时分离到一个引导 chunk 中, // 以便可以在之后正确注入异步 chunk。 optimization: {splitChunks: {name: "manifest",minChunks: Infinity}},plugins: [// 此插件在输出目录中生成 `vue-ssr-client-manifest.json`。 new VueSSRClientPlugin()]
})
webpack.server.config.js
/*** 服务端打包配置 */
const { merge } = require('webpack-merge')
const nodeExternals = require('webpack-node-externals')
const baseConfig = require('./webpack.base.config.js')
const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')module.exports = merge(baseConfig, {// 将 entry 指向应用程序的 server entry 文件 entry: './src/entry-server.js',// 这允许 webpack 以 Node 适用方式处理模块加载 // 并且还会在编译 Vue 组件时, // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。target: 'node',output: {filename: 'server-bundle.js',// 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports) libraryTarget: 'commonjs2'},// 不打包 node_modules 第三方包,而是保留 require 方式直接加载 externals: [nodeExternals({// 白名单中的资源依然正常打包 allowlist: [/\.css$/]})],plugins: [// 这是将服务器的整个输出构建为单个 JSON 文件的插件。 // 默认文件名为 `vue-ssr-server-bundle.json`new VueSSRServerPlugin()]
})
(2)在 npm scripts 中配置打包命令
"scripts": {"build:client": "cross-env NODE_ENV=production webpack --config build/webpack.client.config.js","build:server": "cross-env NODE_ENV=production webpack --config build/webpack.server.config.js","build": "rimraf dist && npm run build:client && npm run build:server"},
运行测试:
npm run build:client
npm run build:server
npm run build
启动应用
server.js
const Vue = require('vue')
const express = require('express')
const fs = require('fs')
const serverBundle = require('./dist/vue-ssr-server-bundle.json')
const clientManifest = require('./dist/vue-ssr-client-manifest.json')
const template = fs.readFileSync('./index.template.html', 'utf-8')
const renderer = require('vue-server-renderer').createBundleRenderer(serverBundle, {template,clientManifest
})const server = express()server.use('/dist', express.static('./dist'))server.get('/', (req, res) => {renderer.renderToString({title: '拉勾教育',meta: `<meta name="description" content="拉勾教育" />`}, (err, html) => {if (err) {return res.status(500).end('Internal Server Error')}res.setHeader('Content-Type', 'text/html;charset=utf8')res.end(html)})
})server.listen(3000, () => {console.log('server running at port 3000')
})

解析渲染流程

(1)服务端渲染

renderer.renderToString 渲染了什么? renderer 是如何拿到 entry-server 模块的?

createBundleRenderer 中的 serverBundle
server Bundle 是 Vue SSR 构建的一个特殊的 JSON 文件

entry:入口 files:所有构建结果资源列表 maps:源代码 source map 信息

server-bundle.js 就是通过 server.entry.js 构建出来的结果文件 最终把渲染结果注入到模板中

(2)客户端渲染 vue-ssr-client-manifest.json

publicPath:访问静态资源的根相对路径,与 webpack 配置中的 publicPath 一致 all:打包后的所有静态资源文件路径 initial:页面初始化时需要加载的文件,会在页面加载时配置到 preload 中 async:页面跳转时需要加载的文件,会在页面加载时配置到 prefetch 中 modules:项目的各个模块包含的文件的序号,对应 all 中文件的顺序;moduleIdentifier和 和all数组中文件的映射关系(modules对象是我们查找文件引用的重要数据)

构建开发模式

基本思路

生产模式

  • npm run build 构建
  • node server.js 启动应用

开发模式

  • 监视代码变动自动构建,热更新等功能
  • node server.js 启动应用

所以我们设计了这样的启动脚本:

"scripts": {...
// 启动开发服务
"dev": "node server.js",
// 启动生产服务"start": "cross-env NODE_ENV=production && node server.js"
}

服务端配置:

1

Part3-4-1 搭建自己的SSR相关推荐

  1. 8.4.1 搭建自己的SSR

    本文为拉勾网大前端高薪训练营第一期笔记 心得体会 SSR是未来前端页面必不可少的一个组成成分,掌握了Vue SSR基本上其他框架都非常类似. 3-4-1 搭建自己的SSR Vue SSR 介绍 是什么 ...

  2. 理解vue ssr原理,自己搭建简单的ssr框架

    理解vue ssr原理,自己搭建简单的ssr框架 前言 大多数Vue项目要支持SSR应该是为了SEO考虑,毕竟对于WEB应用来说,搜索引擎是一个很大的流量入口.Vue SSR现在已经比较成熟了,但是如 ...

  3. Vue.js 框架源码与进阶 - 搭建自己的SSR

    文章目录 一.Vue SSR 介绍 1.1 Vue SSR 是什么 1.2 使用场景 1.3 如何实现 Vue SSR 二.Vue SSR 基本使用 2.1 渲染一个 Vue 实例 2.2 与服务器集 ...

  4. vue前端进阶之SSR篇 --- 搭建简单的SSR框架

    目录 理解ssr SSR的重要性 非SSR SSR 了解vapper 制作简易版脚手架 vue框架 环境区分 目录结构 api router views head信息 打包运行 结束 理解ssr 博主 ...

  5. Vue 服务端渲染(SSR)、Nuxt.js - 从入门到实践

    前言 10月初有幸接到公司官网改版需求,要求采用服务端渲染模式对原网站进行seo优化. 由于团队一直使用的vue技术栈,所以我第一时间想到的就是采用vue 服务端渲染(SSR)来实现该需求,即能减少团 ...

  6. IT:前端进阶技术路线图(初级→中级→高级)之初级(研发工具/HTML/CSS/JS/浏览器)/中级(研发链路/工程化/库/框架/性能优化/工作原理)/高级(搭建/中后台/体验管理等)之详细攻略

    IT:前端进阶技术路线图(初级→中级→高级)之初级(研发工具/HTML/CSS/JS/浏览器)/中级(研发链路/工程化/库/框架/性能优化/工作原理)/高级(搭建/Node/IDE/中后台/体验管理/ ...

  7. 自定义vue SSR

    项目源码地址 : https://github.com/qifutian/learngit/tree/main/vue-ssr 搭建自己的SSR mkdir vue-ssr cd vue-ssr np ...

  8. Nuxt.js(Vue SSR)创建项目到服务器(Nginx+PM2)部署详细流程

    一.什么是 SSR ? SSR 就是 服务器渲染,什么是 服务器渲染?由 服务器 组装好 DOM 元素,生成 HTML 字符串给到浏览器,也就是在浏览器里面是可以看到整个页面的 DOM 源码的. SS ...

  9. umi ssr 之从入门到放弃

    今年7月份的时候用umi搭建了react ssr,发现了不少问题,记录一下,作为后来人选型的参考,是真正的从入门到放弃.如果下面罗列的问题你们自己有办法解决,那可以考虑使用,当然,以下仅供参考,可能官 ...

最新文章

  1. 【组队学习】【35期】SQL编程语言
  2. VUE—从入门到飞起(一)
  3. android token机制_对Android 中的 ANR 进行详解
  4. 【OpenGL】Shader技巧集合
  5. 基于JAVA+SpringMVC+MYSQL的医院后勤管理系统
  6. 【POJ2259】Team Queue(队列,模拟)
  7. 好用小工具及文章推荐推荐
  8. 记录常用的chrome插件
  9. 三峡大学校赛----十万桃花图(线性基)
  10. cocos2d_x之AnySDK接入流程
  11. BIOS视频中断 10号中断详解
  12. 数学与物理桥梁下的鸟瞰
  13. 7-文件IO-阻塞与非阻塞IO
  14. html手机号输入框,手机号输入框自动格式化为344
  15. 基本数据类型 int操作 bool布尔操作 str字符串操作 for in 循环
  16. 获取mumu模拟器日志
  17. windows装linux
  18. 群辉查看硬盘存储占用的方式
  19. java新手案例_java初学者都要掌握的案例
  20. 飞链云版图-文字生成图片

热门文章

  1. HackerRank - C语言 - Introduction - Playing With Characters
  2. Python:绘制数学图形(2)
  3. Cadence orcad 导出网表(.asc文件)
  4. css,sass,scss和less的区别
  5. java switch case 跳转_java 在switch结构中的case1如何跳转到case2
  6. Linux 服务具体解释
  7. iOS 自定义无限循环滚动广告动画控件
  8. RRC协议学习—系统信息(SI)
  9. Vue:前端体系与前后端分离
  10. 记我一次成功的入侵学校网站服务器的黑客行动