如何给多个页面,添加统一的导航栏?我罗列对比了 5 个方案

2022-12-27 08:53:16 浏览数 (2)

背景

之前我开发了一些工具,每个页面是一个html文件,整体是个多页面应用。包括这些:

  • 备忘录: https://tool.hullqin.cn/memo.html
  • URL解析: https://tool.hullqin.cn/url-parser.html
  • 字节解析: https://tool.hullqin.cn/byte-parser.html
  • ProtoBuf解析: https://tool.hullqin.cn/pb-parser.html
  • JSON格式化: https://tool.hullqin.cn/json-formatter.html
  • 表格转换: https://tool.hullqin.cn/table-converter.html
  • 换行符清除: https://tool.hullqin.cn/line-break-remover.html
  • 颜色混合: https://tool.hullqin.cn/color-mixer.html
  • 图片裁剪: https://tool.hullqin.cn/img-editor.html

当时,每一个工具都有一个URL,每个页面只有本工具的内容,没有统一的「导航栏」,这对于工具网站是非常不方便的。所以,我需要加一个统一的导航栏,方便用户在多个页面之间跳转。

我做事情很谨慎,一定要罗列多个方案,再做决策。

我把所有可行的方案都罗列到了本文中,并描述了各个方案的优点、缺点。方便大家遇到相同问题时做决定。

导航栏特点

罗列方案前,你需要知道:

  1. 导航栏是可变的,每当你新做一个页面、修改某页面的标题或URL,都需要更新导航栏。
  2. 所有页面的导航栏,应该具有一致性,更新时要统一更新(否则用户会比较困惑)。

方案一:服务端渲染

这里服务端渲染主要包括2种:

  1. 基于NodeJS框架做的SSR。
  2. 基于其它后端框架模版做的动态渲染。

他们都可以实现这种的效果:用户请求某个页面的html时,后端动态拼接好一份完整的html,返回给前端。在拼接过程中,把导航栏的html片段加进去。

优点

白屏时间短,SEO好。

缺点

  1. 服务端渲染是需要耗费服务端资源的,即使渲染结果可以缓存,我依然不建议浪费这些计算、存储资源。
  2. 服务端需要维护好导航html片段。而服务端代码和前端代码通常不在一个仓库,如果开发者手动更新导航html片段,效率低,容易忘记。即使你做了自动化方式同步,这也涉及到跨仓库同步,不是很方便。
  3. 开发过程中,为了达到跟线上一样的效果,可能还需要启动后端服务,导致本地开发测试不方便。

综上,如果你的网站本身没有服务端渲染,我不建议你仅仅为了增加导航栏而采用该方案。

方案二:前端编译时插入

前端增加编译环节,源代码不写导航栏,编译后,自动在特定位置插入导航栏的html片段。

优点

  • 白屏时间短,SEO好。
  • 可以放在CDN。

特点

  • 需要增加编译环节,可以借助Webpack等工具。

如果不想使用Webpack,也可以像我一样,手写编译脚本(基于NodeJS):

首先是build.js,它遍历src文件夹下的html文件,针对每个html文件,跑一遍函数addNavigation,把结果写入build文件夹。

代码语言:javascript复制
// build.js
const fs = require('fs');
const addNavigation = require('./navigation');
fs.readdirSync('../src').forEach(filename => {
  if (!filename.endsWith('.html')) return;
  const html = fs.readFileSync('../src/'   filename, 'utf-8');
  const newHtml = addNavigation(html, filename);
  fs.writeFileSync('../build/'   filename, newHtml, 'utf-8');
});

然后是navigation.js,它就是针对html源代码做修改,返回新的html片段,已经插入了导航栏html片段。

代码语言:javascript复制
// navigation.js
const config = [
  {name: '备忘录', url: 'memo.html'},
  // ...
];
const getNavigationHtml = (filename) => {
  return '<div>导航栏html片段</div>';
};

const addNavigation = (html, filename) => {
  let newHtml = html;
  const navigationHtml = getNavigationHtml(filename);
  const bodyIndex = newHtml.indexOf('<body>')   6;
  newHtml = newHtml.substr(0, bodyIndex)   navigationHtml   newHtml.substr(bodyIndex);
  return newHtml;
};

module.exports = addNavigation;

为什么这么设计呢?因为addNavigation只是编译的一个环节,之后可以方便的增加addHeaderaddFooter等等。

缺点

  • 每次更新导航栏,需要重新编译所有项目,并重新发布所有页面的html文件。(但它是可接受的,全部重新编译、全部重新发布,完全可以自动化实现,且成本很低)

我个人就是选择了这种方案,参考: github.com/HullQin/tool-hullqin-cn/tree/main/scripts

方案三:前端运行时插入(UMD、模块联邦)

通过script动态引入导航js,运行时插入html片段(即UMD方式,Webpack的模块联邦也属于这种方案)。

为什么必须通过script引入?

因为导航栏的一致性和可变性,开发时它一定是只存了一份代码的。因为本方案不在编译时统一插入,而是在运行时动态插入,所以就需要多个页面引入同一份js文件,动态插入一样的导航栏。

优点

解决了方案二的缺点,每次变更导航栏,只需要重新发布script即可,不需要重新发布其他工具的html。

缺点

  1. 加载速度较慢,可能存在导航栏闪动问题(因为script是异步加载的,展示页面内容时,可能还没下载好导航栏对应script)。
  2. SEO不好。
  3. JS缓存时间不能太久。如果缓存太久导致无法及时自动更新、如果缓存太短导致经常加载速度慢。

如果可以接受这些缺点,这确实是非常好的方案。适合内部平台使用。

方案四:基于框架组件

如果页面整体是同一个项目,同一个框架,那么使用组件是最方便的。

这时候基本不需要决策了,直接无脑用组件吧。

方案五:基于微前端

微前端的初衷正是为了解决巨石应用,也可以让多个应用放到同一个SPA中,切换更流畅。

微前端方案中,通常分为「主应用」和「子应用」。可以把导航栏放在「主应用」中。

优点

  • 框架不受限制。
  • 可以让多页面应用(MPA)体验像单页面应用(SPA)一样(即切换页面时,导航栏不闪烁)。

缺点

  • 重。

如果你的项目本身不是基于微前端的,没有必要为了加导航栏而引入微前端方案。

你可以看看我的网站 tool.hullqin.cn,它没有采用微前端方案,本身是个多页面应用(非SPA)。但因为浏览器有缓存,所以体验非常丝滑,在多个页面之间切换非常快。

方案汇总

方案

框架限制

首屏加载速度

SEO

可维护性

服务端渲染(SSR或模板渲染),统一在html特定位置插入导航html片段

较快

很好

导航html片段在后端项目,需维护好它

前端编译时,统一在html特定位置插入导航html片段

最快

很好

导航html片段在前端项目,需维护好它

通过script动态引入导航js,运行时插入html片段

一般

同上

基于框架组件(React、Vue等)做导航栏

必须统一框架

一般

同上

基于微前端做导航栏,导航属于主应用,工具页面属于子应用

一般

一般

同上

我个人是选择了方案二,代码参考: github.com/HullQin/tool-hullqin-cn

效果如下: tool.hullqin.cn

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注我,交个朋友)。转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这个专栏里分享:《教你做小游戏》。

0 人点赞