webpack实现SSR打包
1、服务端渲染(SSR)
SSR(single slide render)作为新的出现,是因为在SPA(Single-Page Application – 单页应用程序)流行之后,主要是解决前端开发语言来解决SPA出现问题的进一步解决方案和优化页面白屏的用户体验。
服务端优势:
- 所有的模板等资源都存储在服务端
- 内网机器拉取数据更快
- 一个HTML返回所有数据
1.1 SPA
传统SPA应用的优点是前后端分离,后端只负责提供数据,前端加载完之后体验良好,内容改变不需要加载全部页面,缓存数据,减少服务器压力,提高性能。SPA工作的流程是第一次将所有需要的前端搭建的构建脚本资源文件全部下载下来,然后运行脚本异步加载数据,渲染页面,也正是这种特性造成的结果:
首屏慢:第一次加载所有资源,然后才开始执行,那执行到这里优化不好的话,已经是几秒十几秒以后了。 SEO优化差:搜索引擎爬虫爬到这里是空的!搜索引擎抓取的时候是不执行js脚本就跟谈不上什么异步请求了,当然Google最新技术在支持,但是什么时候用上呢? 不能刷新:使用vue之后你会发现,好多页面刷新就意味着页面数据全部消失,前进后对都是消失,就需要自行编写前进后端的逻辑了。 页面复杂度成倍提高,前端开发的要求提高。
1.2 传统前端页面加载流程
- 用户输入网址,浏览器向服务器发出请求,服务器返回html文件。
- 浏览器开始载入html代码,发现有
<link>
标签引用外部CSS文件。 - 浏览器发出CSS文件的请求,服务器返回CSS文件。
- 浏览器继续载入html中的代码,并且CSS文件已经拿到手了,可以开始渲染页面了。
- 发现引用图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码。
- 服务器返回图片文件,HTML页面回流。
- 浏览器发现了
<script>
标签,赶快运行它。 - js执行,如果js有操控页面元素,进行回流或者重绘。
- 渲染HTML文件结束。
- 如果底部有其他js或者css文件看情况再次进行回流或者重绘。
- 页面可完全交互。(页面展示)
1.3 SSR渲染页面过程
- 发送请求。
- 服务器接受并响应。
- 服务器拉取数据和HTML template。
- 服务器渲染。
- 服务器返回渲染完成的页面。(页面展示)
- 浏览器解析页面直接展示。
- 并行加载js和其他资源。
- 页面可完全交互。
1.4 两者的区别
客户端:
- 请求:多个请求HTML、数据等
- 加载过程: HTML&数据串行加载
- 渲染:前端渲染
服务端:
- 请求: 1个请求
- 加载过程:1个并行请求返回数据
- 渲染:服务端渲染
总结:SSR的核心是减少请求,加快数据拉取,服务端渲染减少白屏时间,对SEO友好,客户端渲染是串行加载,服务端渲染是并行加载,在服务端一次性加载完成。
2、webpack配置
新建一个webpack.ssr.js
,在package.json
里面修改srcipts
的值,新增"build:ssr": "webpack --config ./webpack.ssr.js"
,把之前的配置复制到webpack.ssr.js
里面去。
根目录新建server
文件夹,新建index.js
,使用express
框架。
安装:npm install express -D
3、ssr打包的问题
node中没有window对象,将不兼容的组件根据打包环境进行适配,将 fetch 或者 ajax 发送请求的写法改成 isomorphic-fetch 或者 axios
node中无法解析css,可以通过 ignore-loader 忽略略掉 CSS 的解析或者将 style-loader 替换成 isomorphic-style-loader
server.js
的代码
// 对问题的兼容
if (typeof window === 'undefined') {
global.window = {};
}
const fs = require('fs');
const path = require('path');
const express = require('express');
const { renderToString } = require('react-dom/server');
const SSR = require('../dist/search-server');
// 读取模板,就是一个基本的HTML文档 把buffer转换成字符串
const template = fs.readFileSync(path.join(__dirname, '../dist/search.html'), 'utf-8');
// 获取数据
const data = require('./data.json');
const server = (port) => {
const app = express();
app.use(express.static('dist'));
app.get('/search', (req, res) => {
const html = renderMarkup(renderToString(SSR));
res.status(200).send(html);
});
app.listen(port, () => {
console.log('Server is running on port:' + port);
});
};
server(process.env.PORT || 3000);
// 接收一段html字符串参数,然后返回一个处理好格式的HTML字符串
const renderMarkup = (str) => {
const dataStr = JSON.stringify(data);
return template.replace('<!--HTML_PLACEHOLDER-->', str)
.replace('<!--INITIAL_DATA_PLACEHOLDER-->', `<script>window.__initial_data=${dataStr}</script>`);
}
4、统计信息 stats
在打包或者run dev的时候命令行会输出一大堆东西,我们可以通过stats
配置来处理优化
stats
的配置项
errors-only
:只在发生错误时候输出minimal
:只在发生错误或者有新的编译的时候输出none
:没有输出normal
:标准输出verbose
:全部输出
当生产环境的时候可以直接配置在
module.exports = {
stats: 'errors-only'
}
在开发环境
module.exports = {
devServer: {
stats: 'errors-only'
},
}
只设置这个的话,在成功的时候啥都没有,也不太合适,可以使用一个插件friendly-errors-webpack-plugin
,
module.exports = {
plugins: [
new FriendlyErrorsWebpackPlugin()
]
}