跳到主要内容

9 篇博文 含有标签「web」

查看所有标签

为什么选择 PWA?

· 阅读需 2 分钟

PWA 代表渐进式网页应用。早在 2014 年,W3C 发布了服务工作者的草案,随后在 2015 年,Chrome 在生产环境中支持了它。如果我们将服务工作者的出现视为 PWA 的核心技术之一,那么 PWA 的诞生年份就是 2015 年。在关注 PWA 是什么之前,让我们先了解一下我们为什么需要它。

  1. 用户体验。回到 2015 年,前端开发者花费大量时间通过加快初始页面的渲染、使动画更流畅等方式来优化网页。然而,在用户体验方面,原生应用仍然占据优势。

  2. 用户留存。原生应用可以固定在手机的主屏幕上,并通过通知将用户带回应用,而网页应用则无法做到这一点。

  3. 利用设备 API。Android 和 iOS 提供了丰富的设备 API,原生应用可以轻松在用户许可下使用。然而,当时浏览器并未完全支持这些 API。

谷歌的教程 为什么构建渐进式网页应用 将这个问题总结为“广泛覆盖,低参与度”。

网站与原生应用的 UV 和用户时长比较

为了应对移动时代网页应用的劣势,PWA 应运而生。

什么是 PWA?

· 阅读需 4 分钟

当谷歌提出 PWA 时,并没有一个精确的定义。它不是一种特定的技术,而是一种组合技术,旨在改善用户体验。这些技术包括 Web 应用清单、服务工作者、网络推送等。

PWA 的主要特性如下:

  • 可靠性 - 即使在不稳定或断开的网络环境中也能快速加载。
  • 用户体验 - 响应迅速,具有流畅的过渡动画和用户操作反馈。
  • 粘性 - 像原生应用一样,可以添加到主屏幕并接收离线通知。

PWA 本身在两个方面强调了 渐进性

  1. PWA 仍在不断发展;
  2. PWA 向下兼容且不具侵入性。开发者使用新特性几乎没有成本 - 开发者可以逐步将其添加到现有网站中。

谷歌的 "渐进式网络应用检查表" 定义了 PWA 的最低要求。

  • 通过 HTTPS 提供服务。
  • 页面在桌面、平板和移动设备上应具有响应性。
  • 所有 URL 在断开连接时应有内容显示,而不是默认的浏览器页面。
  • 需要将 Web 应用清单添加到桌面。
  • 页面加载更快,延迟更短,即使在 3G 网络上。
  • 在所有主要浏览器中正确显示。
  • 流畅的动画和即时反馈。
  • 每个页面都有自己的 URL。

PWA 的特性

PWA 结合了网络应用和原生应用的优点,给我们带来了以下特性。

  • 渐进性 - 适用于所有浏览器,因为它是在考虑渐进增强的情况下开发的。
  • 连接无关 - 能够利用服务工作者实现离线或低网络连接。
  • 原生体验 - 基于应用外壳模型,应该具有原生应用的交互。
  • 持续更新 - 始终保持最新,没有版本或更新问题。
  • 安全性 - 通过 HTTPS 提供服务。
  • 可索引 - 清单文件和服务工作者可以被搜索引擎识别和索引。
  • 粘性 - 通过推送离线通知等,可以让用户回到您的应用中。
  • 可安装 - 用户可以轻松将网络应用添加到主屏幕或桌面,而无需前往应用商店。
  • 可链接 - 通过链接分享内容,无需下载和安装。

更具体地说,PWA 相较于原生应用的优势是什么?开放性和可索引性。用户几乎无法即时安装原生应用,也无法无缝搜索原生应用。

下表展示了传统网络应用、原生应用和 PWA 在各个特性上的比较。

可安装可链接用户体验用户粘性
传统网络应用
原生应用😐✅️
PWA

重写 Facebook.com

· 阅读需 4 分钟

Facebook 从最初的 PHP 服务端渲染网站发展至今已经 16 个年头,Web 开发的外部环境已经是沧海桑田,在老构架上开发新 Feature 的成本也越来越高。为了得到“App 的体验”以及卓越的性能, 他们用 React 和 Relay 重写了整个主网站,基于两个原则 —— “尽可能少尽可能早”、“提升开发体验以服务用户体验”。

将这两个原则应用到四个主要元素 (CSS,JavaScript,数据,导航) 上,得到如下经验

  1. 改进 CSS
    1. Atomic CSS: 使用 Build 时生成的原子类 CSS 将首页的 CSS 减少 80% - 因为这种 CSS 的条目的数量接近 log N——样式的总量是随着独特的样式增长的,而不是跟着代码里面写的样式和 feature 的数量增长的。我们 Uber 就使用的 Styletron 来做这件事情。
    2. CSS-in-JavaScript: 用类似于 stylex.create({}) 这种方法来为组件生成 Style,跟组件放到一起,以增加可删除性,让样式更容易被维护。
    3. 统一使用 rem 让缩放的时候体验更好, Build 时自动将 px 转化成 rem.
    4. 使用 CSS 变量实现黑暗模式
    5. 使用 HTML 内嵌 SVG 作为图标,解决图标延迟渲染的一闪问题
  2. 分拆 JavaScript 以优化性能
    1. 增量式加载代码,把 500 KB 的总代码,分成 50KB Tier 1,150KB Tier 2, 300KB Tier 3,分别对应骨架内容、首屏内容 、非 UI 内容,然后按需加载。
    2. 只在需要的时候加载实验代码
    3. 根据数据加载相应的代码,比如图片数据加载图片组件、视频数据加载视频组件
    4. 给 JavaScript 的大小做预算,严格监视代码尺寸的变化
  3. 尽早载入数据
    1. 预加载:用 Relay 能够立马知道首屏所需要的数据,在下载代码的同时,也 stream 这些数据
    2. 用 stream 的方式减少 round trips
    3. 延迟加载现在不需要的数据
  4. 定义路由 map 以加速导航
    1. 尽早获得路由的定义
    2. 尽早预先获取资源,在 hover 或者 focus 的时候就开始了,导航变化之后,如果还没有加载完,先用 React Suspense transitions 保留当前页面,只有加载完之后才真正切换。这样就能跟标准的浏览器体验保持一致。
    3. 并行下载代码和数据。通常是先下载代码再下载数据,这样是串行的;FB 让数据和代码在一个 round trip 里面同时下载。

Web 应用交付优化

· 阅读需 6 分钟

两个黄金法则:最小化 1) 延迟 2) 负载

最小化延迟…

  • 减少 DNS 查询

    • 使用快速的 DNS 提供商,平均响应时间(cloud flare < DNS Made Easy < AWS Route 53 < GoDaddy < NameCheap)。注意:某些地区的结果可能有所不同
    • DNS 缓存。TTL 权衡 = 性能 <> 时效性
    • 减少第三方域名数量或使用快速 DNS 的服务(与 HTTP1 的域名分片优化冲突)
    • ==DNS 预取== <link rel="dns-prefetch" href="//www.example.com/" >
  • 重用 TCP 连接。

  • 最小化 HTTP 重定向数量

  • 使用 CDN

    • 例如,Netflix 开发自己的硬件并与本地 ISP 合作提供 CDN
  • 消除不必要的资源

  • 在客户端缓存资源

    1. HTTP 缓存头
      • cache-control 用于 max-age
        • 注意,对于 JS 文件:确保浏览器获取更改的简单方法是使用带有哈希的 output.filename 替换。Webpack 缓存
      • expires
        • 如果同时设置了 Expires 和 max-age,则 max-age 将优先。
    2. last-modified,ETag 头以验证自上次加载以来资源是否已更新
      • 基于时间的 Last-Modified 响应头(不常用,因为 nginx 和微服务)
      • 基于内容的 ETag(实体标签)
        • 当最后修改日期难以确定时,此标签非常有用。
        • 通过哈希生成
    3. 一个常见的错误是只设置上述两个中的一个
  • 在传输过程中压缩资产

    • 使用 JPEG、WebP 而不是 PNG
    • HTTP2 自动压缩头部
    • nginx gzipped

最小化负载…

  • 消除不必要的请求字节

    • 特别是对于 cookies
      • 尽管 HTTP 标准未规定头部/ cookie 的大小限制,但浏览器/服务器通常会强制执行…
        • cookies 的 4KB 限制
        • 头部的 8KB ~ 16 KB 限制
      • cookies 会在每个请求中附加
  • 并行处理请求和响应

    • 当浏览器被资源阻塞时,预加载扫描器会提前查看并提前调度下载:约 20% 的提升

应用协议特定优化

  • HTTP 1.x

    • 使用 HTTP keepalive 和 HTTP 管道化:在同一连接上并行调度多个请求,而无需以串行方式等待响应。
    • 浏览器只能打开有限数量的连接到特定域,因此…
      • 域名分片 = 更多来源 * 每个来源 6 个连接(DNS 查询可能引入更多延迟)
      • 打包资源以减少 HTTP 请求
      • 内联小资源
  • HTTP 2.X

    • 引入二进制帧层后,我们获得 每个来源一个连接,并支持多路复用/流优先级/流量控制/服务器推送,因此移除 1.x 优化…
      • 移除不必要的连接和图像拆分
      • 使用服务器推送:之前内联的资源可以被推送并缓存。

工具

参考文献

怎样才能运气好?

· 阅读需 2 分钟

自证预言:相信自己运气好,自己的运气就会好,因为你会更愿意探索机会、接受挑战、坚持不懈;而不是想到了好的结果单纯的开心而已。

运气好的人有三个性格特征:

  1. 外向
  2. 开放
  3. 放松 (神经质程度低)

怎样制造好运气?

  1. 多参加一些新活动,体验一些新东西
  2. 要相信自己的直觉和兴趣
  3. 要乐观,踢球的时候,多抡几脚球,总有进的时候
  4. 善于在坏事中发现好事

好运气的本质是什么?

创造机会、发现机会、敢于行动

这跟正念(Mindfulness)有什么关系?

正念=跳出自己的主观视角,留意到周围事物的价值

佛学的观点

  • 我们反对“吸引力法则”,因为幻想好结果其实是一种“贪”。
  • 我们提倡系统化对待概率,这其实就是“色即是空”。
  • 我们鼓吹在例行的任务之外发现好运气,实际上是避免了“痴”。
  • 我们用全局思维破解正常化偏误,正是在实践“无我”。
  • 所以谁说佛学没用?你要真擅长佛学,像升官发财逢凶化吉之类的小事儿,根本就用不着求佛祖。

防抖、节流和请求动画帧

· 阅读需 1 分钟

这些是优化 UI 事件处理和使过渡更平滑的网页技术。

  • 防抖:将突发的事件(如按键)聚合为一个事件。
  • 节流:保证每 X 毫秒执行一次。比如每 200 毫秒检查一次滚动位置以触发 CSS 动画。
  • 请求动画帧:节流的替代方案。当你的函数重新计算并渲染屏幕上的元素时,你想要保证变化或动画的平滑。注意:不支持 IE9。

防抖和节流有什么区别?在这里尝试

移动网页的渐进式网络应用(PWA)

· 阅读需 2 分钟

为什么选择渐进式网络应用?

  • 可以通过 Chrome 和 Safari 添加到主屏幕
  • 利用服务工作者离线工作
  • 通过推送通知增加用户参与度
  • 在所有浏览器中新用户的转化率提高 104%,在 iOS 上提高 82%

“渐进式”意味着改进不是二元和终结的,而是逐步演进的。

这是什么?

PWA = 为移动设备优化的网站 + manifest.json + 服务工作者加载和注册

如何将其添加到您的网站?

manifest.json 是简单的部分。将以下内容放入 example.com/manifest.json

{
"short_name": "短名称",
"name": "较长名称",
"icons": [
{
"src": "favicon.png",
"sizes": "192x192 150x150 144x144 64x64 32x32 24x24 16x16",
"type": "image/png"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#de4c4f",
"background_color": "#f3f3f3"
}

并将以下内容添加到 HTML 的 <head>

<link rel="manifest" href="/manifest.json"/>
<link rel="apple-touch-icon" href="/favicon.png"/>

然后在 iOS 和 Android 上,用户可以将该网站添加到主屏幕。

然后 ... 服务工作者的加载和注册

我推荐使用 create-react-app 的服务工作者加载脚本, 它具有良好的安全实践,并采用 优先缓存策略。 并且它还包括注销功能。

注册部分更复杂 - 我们添加了以下 webpack 插件来准备 service-worker.js。

// ...
plugins: [
// ...
new SWPrecacheWebpackPlugin(
{
mergeStaticsConfig: true,
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js',
minify: false,
navigateFallback: '/',
navigateFallbackWhitelist: [/^(?!\/__).*/],
staticFileGlobs: [
`${OUTPUT_DIR}/**`,
],
stripPrefix: OUTPUT_DIR,
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
dynamicUrlToDependencies: {
'/index.html': glob.sync(path.resolve(`${OUTPUT_DIR}/**/*.js`)),
},
}
),
],
// ...

这里的棘手部分是,如果您像我们一样有 SSR - 请小心指定 dynamicUrlToDependencies;否则,缓存可能无法更新。

设计非常大的(JavaScript)应用程序

· 阅读需 4 分钟

非常大的 JS 应用 = 很多开发者 + 大型代码库

如何处理很多开发者?

同理心

什么是 ==高级工程师==?没有初级工程师的高级工程师团队就是工程师团队

  1. 成为高级工程师意味着我能够解决几乎所有别人可能抛给我的问题。
  2. 让初级工程师最终成为高级工程师。

高级工程师的下一步是什么?

  1. 高级: “我知道我会如何解决这个问题”,因为我知道我会如何解决它,我也可以教别人怎么做。
  2. 下一个层次: “我知道别人会如何解决这个问题”

良好的编程模型

人们如何编写软件,例如 react/redux, npm。这里有一个影响所有大型 JS 应用的模型 - 代码分割。

  1. 人们必须考虑打包什么以及何时加载
  2. ==基于路由的代码分割==
  3. 但是,如果这还不够呢?
    1. 懒加载我们网站的每一个组件
    2. Google 是怎么做的?通过渲染逻辑和应用逻辑进行分割。==简单地在服务器端渲染一个页面,然后实际渲染的内容触发下载相关的应用程序包。== Google 不做同构渲染 - 没有双重渲染

如何处理大型代码库?

==代码可移除性/可删除性==

例如,CSS 在代码可移除性方面表现不佳

  1. 一个大的 CSS 文件。里面有这个选择器。谁真的知道它是否仍然与您的应用程序中的任何内容匹配?所以,您最终只是把它留在那里。
  2. 因此,人们创建了 CSS-in-JS

==不惜一切代价避免应用程序的中央配置==

  1. 坏例子
    1. 中央路由配置
    2. 中央 webpack.config.js
  2. 好例子
    1. 去中心化的 package.json

避免中央导入问题:路由器导入组件 A、B 和 C

  1. 为了解决这个问题,做 ==“增强”而不是“导入”==
  2. 然而,开发者仍然必须决定何时增强,何时导入。由于这可能导致非常糟糕的情况,我们使“增强”成为非法,没人可以使用它——只有一个例外:生成的代码。

避免基础包的垃圾堆

  1. 例如,基础包绝不应包含 UI 代码
  2. 通过禁止依赖测试解决此问题
  3. ==最直接的方法必须是正确的方法;否则添加一个测试以确保正确的方法。==

小心抽象

我们必须变得善于找到正确的抽象:同理心和经验 -> 正确的抽象

CORS 与 CSP

· 阅读需 1 分钟
  • CORS 允许站点 A(数据提供者)授权站点 B 从站点 A 读取(可能是私有的)数据(使用访问者的浏览器和凭据)。
  • CSP 允许一个站点防止自身(数据消费者)从意外来源加载(可能是恶意的)内容(例如,作为对 XSS 的防御)。