ssr时如何动态加载和渲染第三方开发者的组件

240 阅读4分钟

背景

  • 电商类sass产品需要有以下几个特性
  1. 需要支持seo
  2. 不同品类客户要求都不一样,很难针对每个客户做特定开发。

如何解决以上两点

  • 对于上面的第一个特性。解决基本的seo问题,我们只需要引入ssr就行

  • 但对于第二个特性,该如何处理呢?

    1. 通常sass服务方会提供一套openApi,比如shopify就提供了一套基于graphql的api用于接口查询
    2. 那前端的技术方案需要满足什么功能
    • 第一点:需要支持ssr
    • 第二点:需要提供上传工具,制定装修规范及装修的数据格式。将装修规范及上传工具等封装到Framework内。供第三方开发者安装和开发
    1. 支持第三方开发者将已经开发完成并打包好的组件发布到cdn,同时将组件的cdn地址与开发者所在的店铺做关联。
    2. 将关联好的组件,同步到开发者所在店铺的装修后台。同步支持装修
    • 第三点:第三方开发者上传到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中返回已经渲染好的结果。做到性能的提升。
    • 那何时更新缓存呢?在店铺后台发布新的装修及首页关联的商品数据有变动时需要更新。