Node + Angular Universal SSR:如何在渲染页面时设置设备宽度
Node + Angular Universal SSR: how to set the device-width when rendering a page
我正在寻找一种方法来使用 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)
]
}));
更新: 如前所述放弃使用 jsdom
,因为它会在呈现的页面上执行脚本,这不是预期的。可能可以使用 runScripts
选项进行调整,但仍然会受到性能影响。渲染字符串上的正则表达式替换更快更安全。下面的示例已更新以反映它。
今天我遇到了同样的问题。 Angular 应用程序启用了通用支持并且 @angular/flex-layout
。
当此应用程序在浏览器上呈现时,@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
regex 字符串替换 以修改呈现的文档。大致看起来像这样:
// 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><meta name="viewport" content="${content}"></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);
}
});
});
然后根据 @angular/flex-layout
的服务器端呈现将根据:
{
matches: true,
mediaQuery: '(min-width: 600px) and (max-width: 959px)',
mqAlias: 'sm',
suffix: 'Sm',
property: ''
}
这是正确的,更有优势,因为响应式组件的样式、布局将完全符合客户的期望。
我正在寻找一种方法来使用 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)
]
}));
更新: 如前所述放弃使用 jsdom
,因为它会在呈现的页面上执行脚本,这不是预期的。可能可以使用 runScripts
选项进行调整,但仍然会受到性能影响。渲染字符串上的正则表达式替换更快更安全。下面的示例已更新以反映它。
今天我遇到了同样的问题。 Angular 应用程序启用了通用支持并且 @angular/flex-layout
。
当此应用程序在浏览器上呈现时,@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 等),那么您可以使用 regex 字符串替换 以修改呈现的文档。大致看起来像这样: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><meta name="viewport" content="${content}"></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);
}
});
});
然后根据 @angular/flex-layout
的服务器端呈现将根据:
{
matches: true,
mediaQuery: '(min-width: 600px) and (max-width: 959px)',
mqAlias: 'sm',
suffix: 'Sm',
property: ''
}
这是正确的,更有优势,因为响应式组件的样式、布局将完全符合客户的期望。