背景
- 电商类sass产品需要有以下几个特性
- 需要支持seo
- 不同品类客户要求都不一样,很难针对每个客户做特定开发。
如何解决以上两点
-
对于上面的第一个特性。解决基本的seo问题,我们只需要引入ssr就行
-
但对于第二个特性,该如何处理呢?
- 通常sass服务方会提供一套openApi,比如shopify就提供了一套基于graphql的api用于接口查询
- 那前端的技术方案需要满足什么功能
- 第一点:需要支持ssr
- 第二点:需要提供上传工具,制定装修规范及装修的数据格式。将装修规范及上传工具等封装到Framework内。供第三方开发者安装和开发
- 支持第三方开发者将已经开发完成并打包好的组件发布到cdn,同时将组件的cdn地址与开发者所在的店铺做关联。
- 将关联好的组件,同步到开发者所在店铺的装修后台。同步支持装修
-
第三点:第三方开发者上传到cdn的组件如何支持服务端渲染。服务端渲染时的安全性如何保证呢?如果第三方组件在ssr时存在性能问题,怎么做才能不会影响整体业务呢?
-
1: 安全性的处理如下。引入
axios
下载cdn上的js代码。通过vm
给组件套上一个沙箱,使用vm.createContext
指定的全局对象。通过jsdom
提供渲染时组建浏览器环境。const vm = require('vm'); const axios = require('axios'); const { JSDOM } = require('jsdom'); async function renderComponent() { // 从 CDN 获取 React、ReactDOM 和第三方 React 组件 const [reactRes, reactDOMRes, componentRes] = await Promise.all([ axios.get('https://unpkg.com/react@17/umd/react.development.js'), axios.get('https://unpkg.com/react-dom@17/umd/react-dom-server.browser.development.js'), axios.get('https://unpkg.com/your-component@version/umd/your-component.js') // 替换为你的组件的 CDN URL ]); // 创建一个新的 JSDOM 实例 const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>', { runScripts: 'outside-only', resources: 'usable' }); // 创建一个新的 JavaScript 环境,指定可访问的全局对象。使得组件渲染时违法访问nodejs的api。保证服务端的安全性 const context = vm.createContext({ window: dom.window, document: dom.window.document, React: undefined, ReactDOM: undefined, YourComponent: undefined // 替换为你的组件的全局变量名 }); // 在新的 JavaScript 环境中执行 React、ReactDOM 和第三方 React 组件 vm.runInContext(reactRes.data, context); vm.runInContext(reactDOMRes.data, context); vm.runInContext(componentRes.data, context); // 创建一个 React 组件 const element = context.React.createElement(context.YourComponent); // 替换为你的组件的全局变量名 // 使用 ReactDOMServer 将 React 组件渲染为 HTML 字符串 const html = context.ReactDOMServer.renderToString(element); return html; }
- 如何在ssr时给第三方的组件传递props呢?
const { JSDOM } = require('jsdom'); const fetch = require('node-fetch'); const dom = new JSDOM(`<!DOCTYPE html><div id="root"></div>`, { runScripts: 'dangerously', resources: 'usable', url: 'http://localhost' }); dom.window.fetch = fetch; const script1 = dom.window.document.createElement('script'); script1.src = 'https://unpkg.com/react@17/umd/react.development.js'; dom.window.document.body.appendChild(script1); const script2 = dom.window.document.createElement('script'); script2.src = 'https://unpkg.com/react-dom@17/umd/react-dom.development.js'; dom.window.document.body.appendChild(script2); const script3 = dom.window.document.createElement('script'); // 第三方组件的cdn地址 script3.src = 'your-component-cdn-link'; // 替换为你的组件的 CDN 链接 // 加载第三方组件 dom.window.document.body.appendChild(script3); script3.onload = () => { dom.window.ReactDOM.render( // 给第三方组件传递props dom.window.React.createElement(dom.window.YourComponent, { prop1: 'value1', prop2: 'value2' }), // 替换为你的组件的名称和 props dom.window.document.getElementById('root') ); };
- 因为sass服务。所有的店铺ssr通常会运行在一个服务内,当第三方组件存在性能问题时,如何不影响其他店铺的运行呢?
const vm = require('vm'); const axios = require('axios'); async function renderComponent() { // 从 CDN 获取第三方组件的源代码 const res = await axios.get('https://unpkg.com/your-component@version/umd/your-component.js'); // 替换为你的组件的 CDN URL // 创建一个新的 JavaScript 环境 const context = vm.createContext({ YourComponent: undefined // 替换为你的组件的全局变量名 }); // 设置一个超时时间 const timeout = 5000; // 5 秒 try { // 在新的 JavaScript 篇境中执行第三方组件的源代码,并设置一个超时时间 vm.runInContext(res.data, context, { timeout }); } catch (error) { // 如果出现错误或超时,打印错误信息 console.error('An error occurred while rendering the component:', error); } // 返回渲染后的组件 return context.YourComponent; // 替换为你的组件的全局变量名 }
-
以上这样做虽然在ssr时给第三方开发的组件套上了沙盒,也限制了组件的运行时间。但同时也多了很多请求。因为我们需要下载这些js,同时不同组件内部也有不同的请求。这么多的请求势必会拖累我们首屏的时间。
整个ssr的性能如何保证
- 上面说过这么多的请求,那怎么提升整个ssr的性能呢?
-
- 采用首页静态化方案
- 在店铺装修后或启用的新的第三方组件后。我们在nodejs后台对当前店铺的首页发起一个请求
- 在nodejs接收到请求后,就开始走上面的ssr流程。整个ssr流程渲染的html字符串存到redis中。再次还有人访问这个店铺时,就直接到redis中返回已经渲染好的结果。做到性能的提升。
- 那何时更新缓存呢?在店铺后台发布新的装修及首页关联的商品数据有变动时需要更新。