代码之家  ›  专栏  ›  技术社区  ›  JayChase

节点+角度通用SSR:如何在呈现页面时设置设备宽度

  •  5
  • JayChase  · 技术社区  · 7 年前

    我正在寻找一种方法,用Angular Universal设置服务器端渲染的设备宽度,以便控制预渲染页面是在移动还是桌面布局中。

    我正在使用核心ngExpressEngine进行渲染(与 universal starter

    const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');
    
    app.engine('html', ngExpressEngine({
      bootstrap: AppServerModuleNgFactory,
      providers: [
        provideModuleMap(LAZY_MODULE_MAP)
      ]
    }));
    
    1 回复  |  直到 3 年前
        1
  •  1
  •   muradm    6 年前

    更新时间: 放弃使用 jsdom 如前所述,因为它在呈现的页面上执行脚本,而这不是预期的。可能可以使用 runScripts 选项,仍将受到性能影响。渲染字符串上的Regex替换更快、更安全。下面的示例更新以反映它。


    今天我遇到了同样的问题。启用了角度应用和通用支撑,以及 @angular/flex-layout

    在浏览器上呈现此应用程序时, ObservableMedia 属于 @角度/弯曲布局 正确报告媒体,例如:

    // browser side MediaChange event
    {
      matches: true,
      mediaQuery: "(min-width: 1280px) and (max-width: 1919px)",
      mqAlias: "lg",
      property: "",
      suffix: "Lg"
    }
    

    在服务器上呈现相同的应用程序时:

    // server side MediaChange event
    {
      matches: true,
      mediaQuery: "all",
      mqAlias: "",
      property: "",
      suffix: ""
    }
    

    所以基本上,服务器端在默认情况下不知道客户端的媒体参数,这是可以理解的。

    如果您有某种传递客户端设备宽度的机制(例如通过cookie、个性化API等),那么您可以使用 jsdom 正则表达式字符串替换 修改呈现的文档。大致如下所示:

    // DON'T USE JSDOM, BECAUSE IT WILL EXECUTE SCRIPTS WHICH IS NOT INTENDED
    // this probably may cache generated htmls
    // because they are limited by the number of media queries
    /*
    function updateMetaViewport(html: string, deviceWidth?: number): string {
      const dom = new JSDOM(html);
      const metaViewport = dom.window.document.head.querySelector<HTMLMetaElement>('meta[name="viewport"]');
      // if deviceWidth is not specified use default 'device-width'
      // needed for both default case, and relaxing rendered html
      metaViewport.content = `width=${deviceWidth ? deviceWidth : 'device-width'}, initial-scale=1`;
      return dom.serialize();     
    }
    */
    
    // INSTEAD REGEX WILL BE SIMPLIER AND FASTER FOR THIS TASK
    // use regex string replace to update meta viewport tag
    // can be optimized further by splitting html into two pieces
    // and running regex replace over first part, and then concatenate
    // replaced and remaining (if rendered html is large enough)
    function updateMetaViewport(html: string, deviceWidth?: number, deviceHeight?: number): string {
      const width = `width=${deviceWidth ? deviceWidth : 'device-width'}`;
      const height = deviceHeight ? `, height=${deviceHeight}` : '';
      const content = `${width}${height}, initial-scale=1`;
      const replaced = html.replace(
        /<head>((?:.|\n|\r)+?)<meta name="viewport" content="(.*)">((?:.|\n|\r)+?)<\/head>/i,
        `<head>$1<meta name="viewport" content="${content}">$3</head>`
      );
      return replaced;
    }
    
    router.get('*', (req, res) => {
    
      // where it is provided from is out of scope of this question
      const userDeviceWidth = req.userDeviceWidth;
      const userDeviceHeight = req.userDeviceHeight;
      // then we need to set viewport width in html
      const document = updateMetaViewport(indexHtmlDocument, userDeviceWidth, userDeviceHeight);
    
      res.render('index.html', {
        bootstrap: AppServerModuleNgFactory,
        providers: [provideModuleMap(LAZY_MODULE_MAP)],
        url: req.url,
        document,
        req,
        res
      }, (err, html) => {
        if (err) {
          res.status(500).send(`Internal Server Error: ${err.name}: ${err.message}`);
        } else {
          // once rendered, we need to refine the view port to default
          // other wise viewport looses its responsiveness
          const relaxViewportDocument = updateMetaViewport(html);
          res.status(200).send(relaxViewportDocument);
        }
      });
    });
    

    然后服务器端渲染 @角度/弯曲布局 将根据:

    {
      matches: true,
      mediaQuery: '(min-width: 600px) and (max-width: 959px)',
      mqAlias: 'sm',
      suffix: 'Sm',
      property: ''
    }
    

    这是正确且更有利的,因为响应组件的样式和布局将完全符合客户的期望。