文章目录
一、Astro
astro是一个服务端渲染框架,但是它本身不仅仅只做服务端渲染的工作,也具备类似hexo或者vitepress的博客生成框架的功能,可以不需要服务端干预,生成完全静态的站点,官方也提供了很多主题。
这个框架主要用于生成以内容为中心的网站,注重首屏加载速度,可交互的响应速度等,它认为js是导致react等框架应用性能下降的原因,所以他要减少90%的js代码体积,加载速度增加40%。
其实该框架就类似于最起初的后端php,java等服务端渲染,但不同之处在于,希望将最终页面效果用静态资源展示,尽量减少js代码(相当于多了一层优化吧)
- 组件群岛: 用于构建更快网站的新 web 架构。
- 服务器优先的API设计: 从用户设备上去除高成本的 Hydration。
- 默认零JS: 没有JavaScript运行时开销来减慢你的速度。
- 边缘就绪: 在任何地方部署,甚至像Deno或Cloudflare这样的全球边缘运行时。
- 可定制: Tailwind, MDX 和 100 多个其他集成可供选择。
- 不依赖特定 UI: 支持 React, Preact, Svelte, Vue, Solid, Lit 等等。
不同渲染方式的对比
ssr框架产生的的目的就是为了加快页面加载的速度,以及seo的优化。js是影响页面加载速度最大的原因,所以不管是哪一个框架,其实都是在于js如何处理。框架把某些操作放在服务端,不单纯是减少浏览器压力,还减少了页面的复杂性,例如服务端渲染使用目录来控制路由,不需要浏览器端渲染所必须得代码(如react-route等库来做管理,还有请求响应处理等),服务端渲染构建出的前端代码量更少。
-
浏览器端渲染页面(js代码生成量最大),需要把框架所有的代码加载,js运行再生成dom,再访问接口获取数据,更新页面。首屏加载后,体验较好。经典的单页面应用
-
nuxt.js等ssr框架(js代码生成量适中),是将部分的页面数据请求放在服务端来生成页面,页面加载速度有所提升,但是页面的响应能力需要js代码完全加载,可以产生类似于浏览器端页面渲染的效果。如果不从地址栏访问,本质还是单页面应用。
-
astro框架是将页面中内容尽可能转化为静态内容(js代码生成量少),至于交互性的动态组件,才会为其生成js,需要在组件上打上标记“client:load”。astro 默认生成不含客户端js的网站。 如果使用前端框架 react、preact、svelte、vue、solidjs、alpinejs 或 lit,astro会自动提前将它们渲染为html,然后再除去所有js。astro使用turbo来处理显示页面之间的无缝过渡,产生类似于单页面应用中的客户端路由体验
技术都是为场景服务的,类似我们管理平台可能就不适合astro,可能next.js或者配置webpack异步加载js,产生的用户体验更好。
二、Astro项目结构
常见的astro项目目录结构类似于以下结构:
├── public
│ ├── robots.txt
│ ├── favicon.svg
│ └── social-image.png
├── src
│ ├── components
│ │ ├── Header.astro
│ │ ├── Button.jsx
│ ├── layouts
│ │ ├── PostLayout.astro
│ ├── pages
│ ├── posts
│ │ ├── post1.md
│ │ ├── post2.md
│ │ ├── post3.md
│ │ └── index.astro
│ ├── styles
│ │ └── global.css
│ ├── astro.config.mjs
│ ├── package.json
│ └── tsconfig.json
public/*
public/*
该目录主要存放非代码、未处理的资源(字体、图标等)。目录用于文件和资源,它不会在 Astro 构建过程中处理。这些文件将不加修改地被直接复制到构建文件夹。
src/components
src/components
可存放Astro组件或是像React或Vue这样的前端组件。该目录只是一个习惯。
src/pages
该目录中的文件负责处理路由、数据加载以及网站中每个页面的整体页面布局,支持以下类型文件:
- .astro
- .md
- .mdx (需要安装 MDX 集成)
- .html
- .js/.ts (可作为服务端点,类似于接口吧)
src/pages/
目录中的每个文件都会根据其文件路径成为网站上的一个端点。
src/pages是Astro项目中必须要有的子目录。没有它,你的网站将没有任何页面或路径!
src/layouts
src/layouts
通常用在 Astro 页面和 Markdown 页面中以定义页面的布局。该目录只是一个习惯。
为避免在每个页面上重复相同的 HTML 元素,你可以将常见的 和 元素移动到自己的布局组件中。
src/styles
在 src/styles
目录下存储你的css或sass文件只是个习惯。你也可将所有样式文件放在另外的目录
astro.config.mjs
该文件可对项目开发、构建和部署进行配置。如果你提供了 root 相对路径或 —root CLI 标志,Astro 将基于你运行 astro 命令的目录来解析。
import { defineConfig } from 'astro/config'
// Astro 会将所有其他相对文件和目录字符串解析为相对于项目根目录的字符串:
export default defineConfig({
// 基于你当前的工作目录解析 "./foo" 目录
root: 'foo',
// 基于你当前的工作目录解析 "./foo/public" 目录
publicDir: 'public',
// 自定义输出文件名
vite: {
build: {
rollupOptions: {
output: {
entryFileNames: 'entry.[hash].js',
chunkFileNames: 'chunks/chunk.[hash].js',
assetFileNames: 'assets/asset.[hash][extname]',
},
},
},
},
})
三、Astro组件群岛
“Astro 群岛“指的是静态 HTML 中的交互性的 UI 组件。一个页面上可以有多个岛屿,并且每个岛屿都被独立呈现。你可以将它们想象成在一片由静态(不可交互)的html页面中的动态岛屿。
群岛的好处有哪些? astro 群岛的最明显的好处就是性能:你网站的大部分区域都被转换为了快速、静态的html,js只为单独组件而被加载。js 是一个加载得最慢的资源。每一个字节都影响着阅读者的体验! 另一个好处是并行加载。在上面的一些假想例子中,重要性更低的图像轮播不应该阻挡更重要的页头部分的加载。这两样东西被并行加载并被分别单独组建,这表明阅读者并不需要等着更沉重的图像轮播加载完毕就可以与页头交互了。 还有更棒的:你可以准确地告诉 Astro 如何以及何时渲染每个组件。如果该图像轮播的加载成本真的很高,你可以附加一个特殊的客户端指令,告诉 Astro 仅在轮播在页面上可见时才加载它。如果用户从未看到它,它永远不会被加载。
Astro组件
astro组件的语法是html的超集,框架维护一个vscode插件:Astro,支持以下功能:
- 为 .astro 文件提供语法高亮
- 为 .astro 文件提供 typescript 类型信息。
- vscode 智能提示提供代码补全和提示
astro组件是由两个主要部分所组成的,组件脚本
和组件模板
,其中 ---
之间的代码就是组件脚本,其中包含模板所需的JavaScript代码:
- 导入其他 Astro 组件
- 导入其他框架组件,如 React
- 导入数据,如 JSON 文件
- 从 API 或数据库中获取内容
- 创建你要在模板中引用的变量
简单的模板用法:
---
import AstroComponent from '../components/AstroComponent.astro';
import ReactComponent from '../components/ReactComponent.jsx';
import someData from '../data/pokemon.json';
const Element = 'div'
const Component = AstroComponent;
// 访问传入的组件参数,如 `<X title="Hello, World"/>`
const {title} = Astro.props;
const visible = true;
// 获取外部数据,可从私有API和数据库中获取
const data = await fetch ('SOME_SECRET_API_URL/users').then (r => r.json ());
---
<!-- 支持 HTML 注释! -->
<h1>你好啊,我是赛利亚!</h1>
<!-- 在组件脚本中使用参数或其他变量: -->
<p>我最喜欢的宝可梦是:{Astro.props.title}</p>
<!-- 混合 HTML 和 JavaScript 表达式,类似于 JSX: -->
<ul>
{data.map ((data) => <li>{data.name}</li>)}
</ul>
<!-- 支持 三元表达式! -->
{visible ? <p>Show me!</p> : <p>Else show me!</p>}
<!-- 渲染为 <div>Hello!</div> -->
<Element>Hello!</Element>
<!-- 渲染为 <MyComponent /> -->
<Component />
关于在astro中元素的事件绑定
<!-- ❌ 无法正常工作! ❌ -->
---
function handleClick () {
console.log("你点击了一个按钮!");
}
---
<button onClick={handleClick}>点击这里什么都不会发生!</button>
<!-- ⭕ 正确写法 ⭕ -->
<button id="button">点击这里</button>
<script>
function handleClick () {
console.log("你点击了一个按钮!");
}
document.getElementById("button").addEventListener("click", handleClick);
</script>
Astro和JSX间的差异
//1. 属性
<div className="box" dataValue="3" /> //jsx
<div class="box" data-value="3" /> //astro
//2. 注释
<!-- 在Astro中,你可以使用标准的HTML注释,而JSX会使用JavaScript风格的注释。-->
组件参数传递
<!-- GreetingHeadline.astro -->
---
// 使用:<GreetingHeadline greeting="你好" name="朋友" />
const { greeting, name } = Astro.props
---
<h2>{greeting},{name}!</h2>
<!-- src/components/GreetingCard.astro -->
---
import GreetingHeadline from './GreetingHeadline.astro';
const name = "Astro"
---
<h1>Greeting Card</h1>
<GreetingHeadline greeting="嗨" name={name} />
<p>希望你有美好的一天!</p>
插槽
<!-- src/components/Wrapper.astro -->
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props
---
<div id="content-wrapper">
<Header />
<slot name="after-header"/> <!-- 带有 `slot="after-header"` 属性的子项在这 -->
<Logo />
<h1>{title}</h1>
<slot> <!-- 没有 `slot` 或有 `slot="default"` 属性的子项在这 -->
<p>当没有子项传入插槽时使用此回退</p>
</slot>
<Footer />
<slot name="after-footer"/> <!-- 带有 `slot="after-footer"` 属性的子项在这 -->
</div>
<!-- src/pages/fred.astro -->
---
import Wrapper from '../components/Wrapper.astro';
---
<Wrapper title="弗雷德的页面">
<img src="https://my.photo/fred.jpg" slot="after-header">
<h2>关于弗雷德的一切</h2>
<p>这里有一些关于弗雷德的资料。</p>
<p slot="after-footer">版权所有 2022</p>
</Wrapper>
通用指令
class:list
<!-- 使用模板指令并根据字符串或对象来生成 class 名: -->
<span class:list={[ 'hello goodbye', { hello: true, world: true }, new Set([ 'hello', 'friend' ]) ]} />
<!-- 输出 -->
<span class="hello goodbye world friend"></span>
set:html
注意该属性可能会导致跨站脚本攻击,需确保信任相关内容
---
const rawHTMLString = "Hello <strong>World</strong>"
---
<h1>{rawHTMLString}</h1>
<!-- 输出:<h1>Hello <strong>World</strong></h1> -->
<h1 set:html={rawHTMLString} />
<!-- 输出:<h1>Hello <strong>World</strong></h1> -->
<!-- 异步加载页面内容 -->
<article set:html={fetch('http://example/old-posts/making-soup.html')}></article>
客户端指令
client:load
// 在页面加载时,立即加载并激活组件的 JavaScript。
<BuyButton client:load />
client:idle
// 一旦页面完成了初始加载,并触发 requestIdleCallback 事件,就会加载并激活组件中的 JavaScript。
// 如果你所在的浏览器不支持 requestIdleCallback,那么就会使用文档 load 事件。
<ShowHideButton client:idle />
client:visible
// 一旦组件进入用户的视口,就加载组件的 JavaScript 并使其激活。
<HeavyImageCarousel client:visible />
client:media
// client:media={string} 一旦满足一定的 CSS 媒体查询条件,就会加载并激活组件的 JavaScript
<SidebarToggle client:media="(max-width: 50em)" />
client:only
// client:only={string} 跳过 HTML 服务端渲染,只在客户端进行渲染。它的作用类似于 client:load,它在页面加载时立即加载、渲染和润色组件。
// 你必须正确传递组件所用框架! 因为 Astro 不会在构建过程中/在服务器上运行该组件
<SomeReactComponent client:only="react" />
脚本和样式指令
is:global
is:global 使 <style>
标签的内容在包含该组件的页面上全面应用,使得astro的css作用域系统失效。
<style is:global>
body a { color: red; }
</style>
is:inline
is:inline
可以让astro将<script>
或<style>
标签原封不动地留在最终输出的html中
is:inline
指令意味着<style>
和<script>
标签:
- 不会被打包到外部文件中。这意味着控制外部文件加载的属性(如 defer)将不起作用。
- 不会被重复使用——该元素会在渲染时出现多次。
- 它的 import/@import/url() 引用不基于 .astro 文件地址进行解析。
- 将进行预处理,例如
- 将在最终输出的 HTML 中准确地呈现它所编写的位置。
- 样式将是全局性的,而不是对组件的范围。
define:vars
该指令将服务器端的变量从组件 frontmatter 传递给客户端的
---
const foregroundColor = "rgb(221 243 228)";
const backgroundColor = "rgb(24 121 78)";
const message = "Astro is awesome!";
---
<style define:vars={{ textColor: foregroundColor, backgroundColor }}>
h1 {
background-color: var(--backgroundColor);
color: var(--textColor);
}
</style>
<script define:vars={{ message }}>
alert(message);
</script>
is:raw
is:raw
会让astro编译器将该元素的任何子项都视为文本,模板语法不再生效
四、Astro的css样式
在astro组件和页面中,默认样式都是限定在当前模块内部,可使用它global指令开放到全局。
<!-- 无作用域,直接传递给浏览器。适用于网站上的所有 <h1> 标签 -->
<style is:global >
h1 { color: red; }
</style>
css变量
<style define:vars={{ foregroundColor, backgroundColor }}>
h1 {
background-color: var(--backgroundColor);
color: var(--foregroundColor);
}
</style>
五、Astro的API端点
Astro 允许你创建自定义的 API 端点(以下简称为“端点”)来提供任何类型的数据。要想要创建自定义端点,请将 .js 或 .ts 文件添加到 /pages 目录。
静态文件端点
端点导出一个 get 函数(可选的以 async 导出),它的唯一参数是一个对象,其具有两个属性(params 和 request)。它返回一个带有 body 的对象,Astro 将在构建时调用它并使用 body 中的内容来生成文件。
// 输出: /builtwith.json
export async function get({params, request}) {
return {
body: JSON.stringify({
name: 'Astro',
url: 'https://astro.build/',
}),
};
}
返回对象还可以具有 encoding 属性。它可以是任何可以被 Node.js 的 fs.writeFile 方法接收的 BufferEncoding。
export async function get({ params, request }) {
const response = await fetch("https://astro.build/assets/press/full-logo-light.png");
const buffer = Buffer.from(await response.arrayBuffer());
return {
body: buffer,
encoding: 'binary',
};
}
params和动态路由
端点同页面一样,都支持 Astro 的动态路由功能。使用中括号包裹参数作为文件名,并导出 getStaticPaths() 函数。
import type { APIRoute } from 'astro';
const usernames = ["张三", "李四", "王五"]
export const get: APIRoute = ({ params, request }) => {
const id = params.id;
return {
body: JSON.stringify({
name: usernames[id]
})
}
};
export function getStaticPaths () {
return [
{ params: { id: "0"} },
{ params: { id: "1"} },
{ params: { id: "2"} },
]
}
这将在构建时生成三个 JSON 端点:/1.json、/2.json 和 /3.json
带端点的动态路由与页面的工作方式相同,但由于端点是一个函数而不是组件,因此 props 是不被支持的。
六、Astro的部署
astro支持两种构建方式,本地构建站点和SSR构建部署
本地构建站点
命令很简单,默认情况下,构建输出将会被放在 dist/. 你可以修改 outDir 配置项改变存储位置。
npm run build
构建目录的index页面就是默认的首页入口
SSR构建
服务端渲染需要修改astro.config.mjs,且要安装ssr适配器。 适配器其实就是astro为各种部署环境适配的构建程序。其中有以下适配器,未来将有更多适配器:
- Cloudflare
- Deno
- Netlify
- Node.js
- Vercel
这里我们单纯以nodejs为例,目前nodejs支持的功能最全面。其他适配器功能还在迭代中。例如”@astrojs/image”目前只支持nodejs。
// astro.config.mjs
import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/nodejs';
export default defineConfig({
output: 'server',
adapter: nodejs(),
});
该模式下构建结果目录会有两个clien和server两个目录,在server目录的运行 entry.mjs,ssr服务即被开启。