Node + Angular Universal SSR:渲染页面时如何设置设备宽度

Jay*_*ase 5 angular-universal angular angular5

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

我正在使用核心 ngExpressEngine 进行渲染(与通用 starter几乎相同)。

    const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main.bundle');

    app.engine('html', ngExpressEngine({
      bootstrap: AppServerModuleNgFactory,
      providers: [
        provideModuleMap(LAZY_MODULE_MAP)
      ]
    }));
Run Code Online (Sandbox Code Playgroud)

mur*_*adm 1

更新:放弃使用,jsdom如之前所解释的,因为它在呈现的页面上执行脚本,这不是有意的。可能可以通过选项进行调整runScripts,但仍然会受到性能影响。正则表达式替换渲染的字符串更快更安全。下面的示例已更新以反映它。


今天我遇到了同样的问题。启用具有通用支持的 Angular 应用程序,并且@angular/flex-layout.

当此应用程序在浏览器上呈现时,ObservableMedia可以@angular/flex-layout正确报告媒体,例如:

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

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

// server side MediaChange event
{
  matches: true,
  mediaQuery: "all",
  mqAlias: "",
  property: "",
  suffix: ""
}
Run Code Online (Sandbox Code Playgroud)

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

如果您有某种传递客户端设备宽度的机制(例如通过 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);
    }
  });
});
Run Code Online (Sandbox Code Playgroud)

那么服务器端渲染方面@angular/flex-layout将根据:

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

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