使用 React 实现快速页面
照片由Kolleen Gladden拍摄,来自Unsplash
我最近为我的书《微前端艺术》创建了网站。在这个页面上,我采取了一种相当保守的方法——制作一个“真正”的单页(即着陆页),力求使其尽可能易于访问且速度快——同时又不牺牲开发者的体验。
当然,现在市面上有很多框架和工具。但我不想花费无数时间学习新东西,最后却被某些框架的限制所束缚。因此,我选择了一种在我看来非常方便、速度极快且轻量级的方法。
技术栈
我选择使用react这个库来编写可重用组件。简而言之,它允许我在页面上编写类似以下的代码:
function Content() {
return (
<>
<Header />
<Grid>
<Book />
<Author />
<Buy />
<Outline />
<Reviews />
<Articles />
<Examples />
<Shops />
<Talks />
<Videos />
<Links />
</Grid>
<Footer />
</>
);
}
export default Content;
它非常容易编写、修改和调整。至于样式方面,我已经安装了[此处styled-components应填写插件名称]。这使我可以将 CSS 代码放在组件旁边,方便应用。简而言之,这使得编写可靠的 CSS 变得非常容易。此外,将来当我省略(甚至删除)组件时,它们的 CSS 也不会出现在输出结果中。
例如,Grid上面所示的组件定义如下:
const Grid = styled.div`
display: grid;
grid-column-gap: 1.5rem;
grid-gap: 1.5rem;
grid-row-gap: 0.5rem;
@media only screen and (max-width: 999px) {
grid-template-areas:
'book'
'buy'
'outline'
'author'
'reviews'
'articles'
'talks'
'videos'
'examples'
'shops'
'links';
}
@media only screen and (min-width: 1000px) {
grid-template-areas:
'book author'
'buy buy'
'outline outline'
'reviews reviews'
'articles videos'
'articles examples'
'articles shops'
'talks links';
grid-template-columns: 1fr 1fr;
}
`;
理论上,网格布局也可以通过 JavaScript 计算——只需指定要包含的元素即可(这也是 CSS-in-JS 方法在这里非常适用的另一个原因)。目前,我对这种硬编码的布局很满意。
就我个人而言,我总是喜欢为我的应用程序添加额外的检查,这就是为什么我会在 TypeScript 中使用整套功能。TypeScript 对 JSX 的处理也相当出色,所以不需要其他任何东西来处理尖括号。
开发环境
为了使整个机制正常运行,我使用了一个自定义的构建脚本。该文件的src/build.tsx核心内容大致如下:
const root = resolve(__dirname, '..');
const dist = resolve(root, 'dist');
const sheet = new ServerStyleSheet();
const body = renderToStaticMarkup(sheet.collectStyles(<Page />));
const dev = process.env.NODE_ENV === 'debug' ? `<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>` : '';
const html = `<!DOCTYPE html>
<html lang="en">
<head>
...
${sheet.getStyleTags()}
</head>
<body>${body}${dev}</body>
</html>
`;
sheet.seal();
addAssets(resolve(__dirname, 'static'));
addAsset(Buffer.from(html, 'utf-8'), 'index.html');
writeAssets(dist);
最重要的是,该表单collectStyles会styled-components创建我们希望用于此页面的内联样式表。该dev变量保存着一个小型刷新脚本,该脚本仅在本地开发期间包含在页面中。
运行该build.tsx文件时,我们使用ts-node。通过调用 ,ts-node src/build.tsx我们可以启动该进程。以下是一些有助于获得更佳体验的其他工具:
- 开发过程中使用LiveServer进行重新加载(即,上面的脚本已经在使用它了)
- Nodemon用于检测开发过程中的更改(例如,一旦我们修改了一个文件,
ts-node进程就应该重新启动)。 - HttpServer用于在开发过程中运行本地 Web 服务器(即,我们需要从某个地方提供页面——
http-server dist这对我们来说就足够了)。
所有这些工具都可以通过以下方式连接在一起concurrently:
concurrently "livereload dist" "http-server dist" "nodemon"
所以当文件发生更改时,我们会:
nodemon检测到变化并重新启动ts-node- 输出结果被放置在
dist livereload检测dist并更新已更改的部分
整个服务都由http-server. 提供。 的配置nodemon如下所示:
{
"watch": ["src"],
"ext": "ts,tsx,json,png,jpg",
"ignore": ["src/**/*.test.tsx?"],
"exec": "NODE_ENV=debug ts-node ./src/build.tsx"
}
关于开发环境的最后一点说明;获取资源时,使用了一组自定义的Node.js模块处理程序:
function installExtension(ext: string) {
require.extensions[ext] = (module, filename) => {
const content = readFileSync(filename);
const value = createHash('sha1').update(content);
const hash = value.digest('hex').substring(0, 6);
const name = basename(filename).replace(ext, `.${hash}${ext}`);
assets.push([content, name]);
module.exports.default = name;
};
}
extensions.forEach(installExtension);
每个资源都会被添加到资源集合中,并复制到指定dist文件夹。资源还会以模块的形式呈现,并在 Node.js 中默认导出。这样,我们就可以编写如下代码:
import frontPng from '../assets/front-small.png';
import frontWebp from '../assets/front-small.webp';
无需考虑其他因素。所有资源都经过正确哈希处理,并由 Node.js 处理。无需打包工具。
CI/CD
我使用 GitHub Actions 来部署页面。这非常方便,因为代码仓库本身就托管在 GitHub 上。
整个工作流程都放在.github/workflows/node.js.yml文件中。这里有两个重要的步骤:
- 准备/建造一切
- 发布所有内容(正确的分支是
gh-pages)
第一步我们使用:
- name: Build Website
run: |
npm run build
echo "microfrontends.art" > dist/CNAME
cp dist/index.html dist/404.html
它会使用特殊文件自动准备自定义域名CNAME。所有输出都会保存在dist文件夹中。然后,这些输出会被推送到gh-pages分支。
同样,我决定复制一份index.html文件404.html。如果用户访问的页面不存在,则会提供这份副本。这种机制对于大多数单页应用(SPA)至关重要——虽然在这个例子中我们实际上并不需要它,但它比标准的 GitHub 404 页面要好。
第二步是将所有内容推送到gh-pages分支。为此,您可以使用该gh-pages工具。
- name: Deploy Website
run: |
git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
npx gh-pages -d "dist" -u "github-actions-bot <support+actions@github.com>"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
重要的是,您需要指定GITHUB_TOKEN环境变量。这样,命令才能真正推送代码。
现在管道部分就全部完成了——页面可以上线了,并且会随着我每次推送的内容而更新。
表现
那么这个小页面表现如何呢?结果相当不错。您可以访问web.dev/measure自行查看。
要使每列都达到 100,还需要一些技巧。例如,不要只使用标签,而img应该使用picture多个数据源。这也是为什么选择react合适的数据源是明智之举的另一个原因:
interface ImageProps {
source: string;
fallback: string;
alt?: string;
width?: number;
height?: number;
}
function getType(file: string) {
return `image/${file.substring(file.lastIndexOf('.') + 1)}`;
}
function Image({ source, fallback, alt, width, height }: ImageProps) {
return (
<picture>
<source srcSet={source} type={getType(source)} />
<source srcSet={fallback} type={getType(fallback)} />
<img src={fallback} alt={alt} width={width} height={height} />
</picture>
);
}
export default Image;
有了这个小组件,我们就可以编写如下代码了
<Image
source={frontWebp}
fallback={frontPng}
alt="The Art of Micro Frontends Book Cover"
width={250}
height={371}
/>
这一点将按上述方式应用。此外,非常重要的一点是,我们指定了图像的宽度和高度。理论上,我们也可以在渲染时动态计算这些值——但由于页面只有 3 张图片,这样做实在得不偿失。
结论
编写简单的网站并不需要很复杂。你不需要学习很多新知识。实际上,大多数情况下,现有的知识就足够了。
我展示的这个页面轻松获得了最佳分数和性能——毕竟,它是最小的软件包,却能提供最佳的开发体验。
文章来源:https://dev.to/florianrappl/fast-pages-with-react-27og
