从 create-react-app 到 PWA
作者:John Reilly✏️
渐进式 Web 应用 (PWA) 是一个非常棒的想法(名字有点糟糕)。你可以使用 Web 技术构建一个适用于所有设备和各种尺寸的应用。它可以通过 Web 访问,也可以显示在 Android/iOS 设备的主屏幕上。该应用可以离线工作,启动时显示启动画面,还可以接收通知。
PWA 可以为您的企业节省成本。如果您想为用户提供更佳的应用体验,那么另一种选择是使用三种不同的技术(一种用于 Web,一种用于 Android,一种用于 iOS)构建同一款应用。
选择这种方式,成本和复杂性的倍增难以避免。这通常会导致团队分裂,因为每个人都在不同的堆栈上工作。因此,很容易失去一定的专注力。PWA 可以在这方面提供帮助。它们不仅从开发者的角度,而且从资源配置的角度,都是一个极具吸引力的替代方案。
然而,PWA 的缺点是它们比普通的 Web 应用更复杂;从头开始编写一个 PWA 就没那么简单了。不过,构建 PWA 有一些简单的入门指南,可以帮助你走上成功之路。这篇文章将重点介绍其中之一——如何使用 React 和 TypeScript 从零开始构建你自己的 PWA。
请注意,本文假定您了解以下内容:
- 反应
- TypeScript
- 节点
从控制台到 Web 应用程序
要创建 PWA,我们将使用create-react-app
。这个优秀的项目长期以来一直内置对 PWA 的支持。近几个月来,这种支持已经成熟到令人满意的水平。要使用 创建一个 TypeScript React 应用create-react-app
,请npx
在控制台中输入以下命令:
npx create-react-app pwa-react-typescript --template typescript
这将为您构建一个使用 TypeScript 构建的 React Web 应用。您可以使用以下命令在本地进行测试:
cd pwa-react-typescript
yarn start
从 Web 应用到 PWA
从 Web 应用到 PWA 非常简单——只需选择离线行为即可。如果你index.tsx
在新建的项目中打开该文件,你会发现以下代码:
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
正如提示所示,替换serviceWorker.unregister()
为serviceWorker.register()
,你就拥有了一个 PWA。太棒了!这是什么意思?好吧,引用文档的话:
- 所有静态网站资源均已缓存,以便您的页面在后续访问时快速加载,无论网络连接如何(例如 2G 或 3G)。更新将在后台下载。
- 无论网络状态如何,您的应用都能正常工作,即使离线也能正常工作。这意味着您的用户无论身处万米高空还是在地铁上都能使用您的应用。
…它将负责生成一个 Service Worker 文件,该文件会自动预缓存您所有的本地资源,并在您部署更新时保持其更新。Service Worker 将使用缓存优先策略处理所有本地资源请求,包括HTML导航请求,确保您的 Web 应用即使在速度慢或不稳定的网络上也能始终保持快速运行。
在底层,create-react-app
这是通过使用名为Workbox的技术来实现的。Workbox 将自己描述为“一组库和 Node 模块,可以轻松缓存资产并充分利用用于构建渐进式 Web 应用的功能”。
Google 的优秀员工深知,编写自己的 PWA 可能颇具挑战性。需要配置和注意许多新行为;因此很容易犯错。Workbox 可以实现默认的缓存/离线行为策略,并通过配置进行控制,从而帮助您简化开发流程。
使用Workbox
in的一个缺点create-react-app
是(就像 in 中的大多数东西一样create-react-app
),如果默认设置无法满足你的需要,那么你几乎没有空间进行自定义配置。这种情况将来可能会改变——事实上,有一个开放的 PR 正在添加这项支持。
图标、启动画面和 A2HS,天哪!
但 PWA 之所以被称为 PWA,不仅仅是因为它拥有离线体验。其他重要因素包括:
- 该应用程序可以添加到您的主屏幕(A2HS,又称“已安装”)
- 该应用程序具有可自定义的名称和图标
- 应用程序启动时向用户显示启动画面
以上所有内容均已包含在内create-react-app
。让我们开始自定义它们。
首先,我们需要为应用命名。启动应用index.html
,将其替换<title>React App</title>
为<title>My PWA</title>
。(您可以随意想一个比我建议的更有想象力的名字。)接下来,打开manifest.json
并替换:
"short_name": "React App",
"name": "Create React App Sample",
和:
"short_name": "My PWA",
"name": "My PWA",
你的应用现在有了名字。你可能会问:这个manifest.json
文件是什么?好吧,引用 Google 的一位好心人的话:
网页应用清单是一个简单的 JSON 文件,它向浏览器告知您的网页应用的信息,以及它在“安装”到用户的移动设备或桌面设备后应如何运行。Chrome 浏览器需要有清单才能显示“添加到主屏幕”提示。
典型的清单文件包括有关应用程序名称、应用程序应使用的图标、
start_url
应用程序启动时应从哪里开始等信息。
所以这manifest.json
基本上是关于你的应用的元数据。它现在看起来应该是这样的:
{
"short_name": "My PWA",
"name": "My PWA",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
您可以使用上述属性(以及其他尚未配置的属性)来控制应用的行为。例如,如果您想替换应用使用的图标,只需执行以下操作:
- 将新的徽标文件放入
public
文件夹中 - 更新对它们的引用
manifest.json
- 最后,对于较旧的 Apple 设备,
<link rel="apple-touch-icon" ... />
更新index.html
我们在哪里?
到目前为止,我们已经完成了一个基本的 PWA 开发。它是可安装的。您可以在本地运行它并使用 进行开发yarn start
。您可以使用 构建它并进行部署yarn build
。
然而,从某种意义上来说,它并不像一个 Web 应用,因为它不支持不同的页面/URL。我们通常会以这种方式拆分我们的应用。现在就让我们这样做。我们将使用react-router
React 的默认路由解决方案。要将它添加到我们的项目中(以及 TypeScript 所需的类型定义),我们使用:
yarn add react-router-dom @types/react-router-dom
现在让我们将应用程序拆分成几个页面。我们将App.tsx
用以下内容替换现有的:
import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import About from "./About";
import Home from "./Home";
const App: React.FC = () => (
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
export default App;
这将是我们的根页面。它负责渲染react-router
我们想要服务的页面,并提供允许用户导航到这些页面的链接。在进行更改时,我们的测试会中断(该测试检查了一个我们现已删除的链接),因此我们将像这样修复它:
用以下代码替换App.test.tsx
:
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders about link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/about/i);
expect(linkElement).toBeInTheDocument();
});
您可能已经注意到,App.tsx
我们在 new 中导入了两个新组件(或页面):About
和Home
。让我们创建它们。首先,About.tsx
:
import React from "react";
const About: React.FC = () => (
<h1>This is a PWA</h1>
);
export default About;
然后,Home.tsx
:
import React from "react";
const Home: React.FC = () => (
<h1>Welcome to your PWA!</h1>
);
export default Home;
代码拆分
现在我们已经将应用拆分成多个部分,接下来我们也要拆分代码。缩短 PWA 加载时间的一个好方法是确保代码不会构建到大文件中。目前,我们的应用构建了一个single-file.js
。运行yarn build
,你会看到如下所示的内容:
47.88 KB build/static/js/2.89bc6648.chunk.js
784 B build/static/js/runtime-main.9c116153.js
555 B build/static/js/main.bc740179.chunk.js
269 B build/static/css/main.5ecd60fb.chunk.css
注意这个build/static/js/main.bc740179.chunk.js
文件。这就是我们的single-file.js
。它代表了构建构成我们应用程序的 TypeScript 文件的编译输出。它会随着我们应用程序的增长而不断增长,最终从用户加载速度的角度来看会成为问题。
create-react-app
基于 webpack 构建。webpack 对代码拆分提供了出色的支持,因此create-react-app
默认支持它。让我们将它应用到我们的应用中。同样,我们将进行以下更改App.tsx
。
我们之前有:
import About from "./About";
import Home from "./Home";
我们将其替换为:
const About = lazy(() => import('./About'));
const Home = lazy(() => import('./Home'));
这是 React 中延迟加载组件的语法。你会注意到,它内部使用了动态import()
语法,webpack 将其用作“分割点”。
在 React 等待动态导入解析时,我们还需要给它一些渲染内容。在<Router>
组件内部,我们也将添加一个<Suspense>
组件:
<Router>
<Suspense fallback={<div>Loading...</div>}>
{/*...*/}
</Suspense>
</Router>
组件<Suspense>
将在<div>Loading...</div>
等待路由代码动态加载的同时进行渲染。因此,最终App.tsx
组件看起来如下所示:
import React, { lazy, Suspense } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
const About = lazy(() => import("./About"));
const Home = lazy(() => import("./Home"));
const App: React.FC = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Suspense>
</Router>
);
export default App;
现在这是一个代码拆分的应用程序。我们如何知道呢?如果我们yarn build
再次运行,我们会看到如下内容:
47.88 KB build/static/js/2.89bc6648.chunk.js
1.18 KB (+428 B) build/static/js/runtime-main.415ab5ea.js
596 B (+41 B) build/static/js/main.e60948bb.chunk.js
269 B build/static/css/main.5ecd60fb.chunk.css
233 B build/static/js/4.0c85e1cb.chunk.js
228 B build/static/js/3.eed49094.chunk.js
请注意,我们现在有多个*.chunk.js
文件:我们的初始文件main.*.chunk.js
,然后3.*.chunk.js
是代表文件Home.tsx
,以及4.*.chunk.js
代表文件Home.tsx
。
从现在开始,随着我们继续构建我们的应用程序,我们将采用一种很好的方法来确保用户根据需要加载文件,并且这些文件不会太大——可扩展的出色性能。
部署 PWA
现在我们已经完成了基本的 PWA 部署,让我们开始部署,让外界能够欣赏它。我们将使用Netlify来实现这一点。
我们的 PWA 的源代码位于 GitHub 上。
我们将登录 Netlify,点击“创建新站点”选项,然后选择 GitHub 作为提供商。我们需要授权 Netlify 访问我们的 GitHub。
您可能需要单击“在 GitHub 上配置 Netlify”按钮来授予 Netlify 访问您的 repo 的权限,如下所示:
然后,你就可以在 Netlify 中选择你的仓库了。Netlify 提供的所有默认设置都应该适用于我们的用例:
让我们点击神奇的“部署站点”按钮!几分钟后,您就会发现 Netlify 已经部署了我们的 PWA。
如果我们浏览 Netlify 提供的 URL,就能看到已部署的 PWA 的实际效果。(您还可以设置自定义域名,这通常是在类似这样的简单演示之外才需要的。)重要的是,这将通过 HTTPS 提供,以便我们的服务工作线程能够正常运行。
既然我们知道它已经存在,那就让我们看看我们构建的内容在专业人士眼中表现如何。我们将对我们的 PWA 运行 Google Chrome DevTools Audit:
对于我们的 PWA 来说,这是一个良好的开端!
全面了解生产 React 应用程序
调试 React 应用可能很困难,尤其是当用户遇到难以复现的问题时。如果您对监控和跟踪 Redux 状态、自动显示 JavaScript 错误以及跟踪缓慢的网络请求和组件加载时间感兴趣,请尝试 LogRocket。
LogRocket就像 Web 应用的 DVR,可以记录 React 应用上发生的所有事件。您无需猜测问题发生的原因,而是可以汇总并报告问题发生时应用程序的状态。LogRocket 还可以监控应用的性能,并报告客户端 CPU 负载、客户端内存使用情况等指标。
LogRocket Redux 中间件包为您的用户会话增加了一层额外的可见性。LogRocket 会记录 Redux 存储中的所有操作和状态。
实现 React 应用程序调试方式的现代化 —开始免费监控。
从 create-react-app 到 PWA 的文章最先出现在LogRocket 博客上。
鏂囩珷鏉ユ簮锛�https://dev.to/bnevilleoneill/from-create-react-app-to-pwa-4e7