AstroJS 启动!

最近有一个官网开发的需求,本来是准备利用老的技术栈,快速开发、快速结束。比如说找一个 PHP 或者 JSP 技术栈的成熟 CMS 框架。前端只需要写一个主题模板,然后用插值表达式做一下循环和动态内容的渲染就行了。

可团队内没有 PHP 技术栈。Java 小伙伴也忙的要死,没有时间现学一个 CMS 框架来配合我。但项目上线时间又安排的非常近,没办法一直等。
就想着先使用有 SSG 功能的前端全栈框架先把项目搞起来,把需求先做完。后面再看情况是做 SSR 也行。如果不做 SEO 没有很多文章发布的场景,也可以仅靠 SSG 和钩子触发自动部署即可。

正巧在播客里面听到一个新的全栈框架 Astro,满足了我上便提到的 SSG 和 SSR 需求。前期可以通过撰写 markdown 文档作为 content 资源来满足项目前期的文章发布需求。

而且 Astro 提出的 群岛 概念非常让我感兴趣!

可以在同一个项目中支持使用多个框架(如 Vue、React、Preact、Svelte 和 SolidJS)。

现阶段我还用不上 群岛 这个功能。虽然非常吸引我,但Astro 提供的基础功能就可以完全覆盖我的需求了。其实选择 Astro 也是因为被安利到了 群岛 功能。
有了这次的开发经验,后面我也可以把博客迁移到 Astro,这样一些框架的学习和实践就直接可以在博客中应用上了。比如说使用新框架实现某一个小功能组件之类的。


🔨 Install and set up

Astro 的安装和初始化非常的方便,就没有什么好说的了。直接 npm create astro@latest 之后按照引导创建项目就可以了。也可以 使用主题或起始模板 找一个符合心理预期的模板一步到位。

我的话,没有套用社区现有的主题模板,而是选择使用的官方的Blog模板。因为是当前的新项目有专门的UI设计,只能靠自己手撸主题和布局,有一个基础内容发布的目录结构即可。

🔧 配置 Astro

配置项的部分就是直接看文档 👉 配置参考 | Docs
找到一些自己需要的配置项开启就好了。因为我这边的现在需求是 SSG,那么主要就是配置 output 属性为 static,以及配置 trailingSlash 属性为 always(默认生成的URL有 尾斜杠)。

其他的就是按照自己的需求,开启扩展集成功能,比如说我需求的 Tailwind 集成 和 sitemap 集成,都有很完善的说明文档,按照指引完成配置就可以了。

👇我的项目的整体结构示意:

│  astro.config.mjs
│  package.json
│  postcss.config.js
│  tailwind.config.js
│  tsconfig.json
├─public/                           # 静态资源目录
│  │  favicon.webp
│  │  logo.webp
│  ├─img/
│  │   ...
│  └─locales/
│      ...
└─src/                              # 项目代码目录
    │  env.d.ts
    ├─components/                   # 可重用组件目录
    │      Pagination.astro
    │      ...
    ├─content/                      # 内容集合目录
    │  │  config.ts
    │  └─news/
    │      └─news-title/
    │              index.mdx
    ├─layouts/                      # 布局组件目录
    │      FooterBar.astro
    │      HeaderBar.astro
    │      Layout.astro
    │      ...
    └─pages/                        # 页面组件目录
        │  index.astro
        │  robots.txt.ts
        ├─about/
        │      index.astro
        ├─news/
        │      [...slug].astro
        │      [page].astro
        └─services/
            └─page-title/
                    index.astro

从目录结构中可以看到特殊单页,比如说关于我们、服务介绍之类,就可以直接在 pages 里面创建对应的页面组件,就和我们现在开发 Vue SFC 组件是一样的。
中间也可以使用 JSX 来做一些特殊处理(比如说分页列表页面)。当然你想直接使用 Vue SFC 的方式来开发也是可以的,安装 @astrojs/vue 集成即可。

内容集合部分考虑到文章会有图片展示的需求,所以每一篇文章都是以目录形式创建的,这样图片资源在 markdown 内部直接用相对路径引用就行,在同一个文章目录下维护起来会方便很多。

🚗 运行和编译

这个没有什么好说的,就是如果项目配置没有问题,按照 npm run dev 和 npm run build 编译就行了。

开发时会有一些基于TS类型检查的提醒,稍微注意一下就好(也可以配置不开启)。


🧰 遇到的问题!

❔ 文章内容中的图片内容应该如何放置和使用

就如同我在上面提到的这样使用目录管理的文章内容。直接就可以把文章图片放到对应的文章目录下,然后使用相对目录引用就行。Astro 会自动收集处理,我们不需要关注。

![Alt text](./full/or/relative/path/of/image)

其他部分的图片内容,比如说 代码围栏 中声明的比如说封面图之类的。
可以查看 内容集合中的图像 这部分了解具体使用和配置方法。

但是可能会遇到 Lint 不通过情况,比如说我就没办法使用内置的 <Image /> 组件在分页列表中渲染文章的封面图,就会提示异常:

// 不能将类型“{ class: string; src: string; alt: string; }”分配给类型“IntrinsicAttributes & Props”。
// 不能将类型“{ class: string; src: string; alt: string; }”分配给类型“IntrinsicAttributes & { [x: `data-${string}`]: any; title?: string; children?: unknown; class?: string; 'class:list'?: string | Record<string, boolean> | Record<any, any> | Iterable<...> | Iterable<...>; ... 203 more ...; inferSize?: false; }”。ts(2322)
(alias) const Image: (_props: Props) => any
import Image

因为我在代码围栏中只是声明了一下文章目录下图片资源的相对路径,并不是使用 import 导入的图片,所以没通过TS的类型检查。这个可以在 #src (必须) - 属性 - 图像 | Docs 部分中查看到具体说明。

如果要解决的话得调整很多内容。并且使用 import 导入图片资源,也不方便非技术人员后期维护 markdown 文档。所以我就放弃了使用内置的 <Image /> 组件,直接使用HTML原生的 <img /> 组件了。

💡 但如果你提示不一样,如下方这样的警告。那么是你忘记使用 import { Image } from 'astro:assets' 引入 Astro 内置的图片组件了。

// 不能将类型“{ src: string; alt: string; }”分配给类型“IntrinsicAttributes & number”。
// 不能将类型“{ src: string; alt: string; }”分配给类型“number”。ts(2322)
var Image: new (width?: number, height?: number) => HTMLImageElement

❔ 文章列表如何分页

导出的 getStaticPaths 函数有一个 paginate 方法,使用 paginate 函数并传入 pageSize 参数,就可以实现分页效果了。

// src/news/[page].astro
export const getStaticPaths = (async ({paginate}) => {
  const posts = (await getCollection('news')).sort(
    (a, b) => a.data.publishDate.valueOf() - b.data.publishDate.valueOf()
  );
  return paginate(posts, { pageSize: 10 });
}) 

const { page } = Astro.props;

❔ 使用 @astrojs/sitemap 动态生成 robots.txt 没有生效

@astrojs/sitemap 是支持动态生成 robots.txt 的,创建一个 robots.txt.ts 的文件就可以在编译时期在构建的产物中生成 robots.txt 文件了,内容物就是你自己书写的内容。
👇 比如说文档中的示例,就是按照 astro.config.mjs 中配置的 site 作为基准URL来生成新的URL链接,

import type { APIRoute } from 'astro';

const robotsTxt = `
User-agent: *
Allow: /
Sitemap: ${new URL('sitemap-index.xml', import.meta.env.SITE).href}
`.trim();

export const GET: APIRoute = () => {
  return new Response(robotsTxt, {
    headers: {
      'Content-Type': 'text/plain; charset=utf-8',
    },
  });
};

如果没有在构建的时候生成,那么可能你的 robots.txt.ts 文件有问题。比如说我就很粗心的把 robots.txt.ts 文件放到了 /src 目录下,而不是 /src/pages/ 目录下。


❔ SSG的内容如何国际化

Astro 的国际化,官方是有内置的指引的 👉 添加 i18n 功能 | Docs。
会按照目录结构生成多语言的编译后产物。

所以我们需要调整项目的目录结构,比如说 content 的目录,就需要调整为👇

src/
  └─content/
      └─news/
          ├─en/
          │      post-1.md
          │      post-2.md
          └─news/
                  post-1.md
                  post-2.md

然后 pages 目录也需要同步调整增加一层 [lang] 目录。
比如详情页: src/pages/news/[...slug].astro 👉 src/pages/[lang]/news/[...slug].astro

不过还有一些细节部分,我还没有实际运用到。
比如说多语言的图片资源,语言改变之后图片也需要同步调整为对应本地化的图片内容。
这些需要实际项目运用到的时候再补充完整了。项目是准备上多语言,但是还没有安排开发计划。