Astro 续作
[! 警告] 在V0吗? 升级. 以前每个版本
1.0.0将序列化的数据写入页面而不逃避<因此,一个字符串包含</script>在数据中提早关闭元素,其余以 HTML 运行。 如果其中任何数据来自用户输入,则视其为可开发数据。 固定在1.0.0,其唯一破碎的更改是 TypeScript 之一 — 见 逃脱 & XSS 财务报告和审定财务报表 升级到 v1。 。 。 .
[!注] V1 中新建 —— 说
deserialize<T>现在默认为unknown改为any,所以没有类型参数的调用在读取结果属性时不再进行键入检查。 见 升级到 v1。 。 。 .
用于序列化服务器数据供客户端使用的工具.
Serialize- 需要Astro组件id财务报告和审定财务报表datadeserialize()- 用于客户端的功能id字符串并返回data对象
通过 npm 安装
在你身边 天体 项目:
npm i @ayco/astro-resume
需要Astro 4或更新.
使用量
基本原始数据的序列化和去序列化
---
import Serialize from "@ayco/astro-resume";
const data = {
hello: 'world',
}
---
<Serialize id="my-data" data={data} />
<script>
import {deserialize} from '@ayco/astro-resume';
const data = deserialize('my-data');
console.log(data) // {hello: 'world'}
</script>
没有类型参数 data 实值 unknown 足够登入或传入,但不能读取属性。 说出要这样做的类型.
类型安全
deserialize<T>() 返回时 T,默认为 unknown。可以定义数据的类型,并在客户端脚本中使用.
---
import Serialize from "@ayco/astro-resume";
const data = {
hello: 'world',
isOkay: true
}
// define the type of data to be serialized
export type Data = typeof data;
---
<Serialize id="my-data" data={data} />
<script>
import {deserialize} from '@ayco/astro-resume';
/**
* reuse the type in the client
* assuming this component's name is `ThisComponent.astro`
*/
import type {Data} from './ThisComponent.astro';
const data = deserialize<Data>('my-data');
console.log(data) // {hello: 'world', isOkay: true}
</script>
自定义解析器已键入 (serialized: string) => T,同样的 T 呼叫回来。 这么说吧 deserialize<Data>('my-data', parse) 检查您的解析器是否生成 Data,以及 deserialize('my-data', myParser) 推断 T 从 myParser 而不是掉头 unknown。 。 。 .
将全部 Astro.props 传递给客户端
如果您需要将所有组件道具制成客户端脚本 :
---
import Serialize from "@ayco/astro-resume";
export interface Props {
hello: string;
isOkay: boolean;
}
---
<Serialize id="preferences" data={{...Astro.props}} />
<script>
import {deserialize} from '@ayco/astro-resume';
import type {Props} from './ThisComponent.astro';
const {hello, isOkay} = deserialize<Props>('preferences');
console.log(hello, isOkay);
</script>
将服务器数据序列化一次, 访问到任何地方
如果您共享了需要从服务器初始化并在客户端多个地方访问的数据,则可以使用 Serialize 一次和一次 deserialize 只要Astro组件在同一页中,它们就属于任何数量.
在这个例子中,一个 AppConfig 对象在index.astro 中被构建并序列化,在child Astro 组件中访问.
在索引.astro:
import Serialize from "@ayco/astro-resume";
const appConfig = {
someClientSideKey: '1234hello',
}
export type AppConfig = typeof appConfig;
---
<Serialize id="app-config" data={appConfig} />
<Child />
在 Child.astro 中: (中文(简体) )
<h1>I'm a child. I have access to the appConfig in index!</h1>
<GrandChild />
<script>
import {deserialize} from '@ayco/astro-resume';
import type {AppConfig} from '..pages/index.astro';
const data = deserialize<AppConfig>('app-config');
// ... do something with the app config
</script>
在Grand Child.astro: (原始内容存档于2019-09-28)
<h1>I'm a grand child. I also have access to the appConfig in index!</h1>
<script>
import {deserialize} from '@ayco/astro-resume';
import type {AppConfig} from '..pages/index.astro';
const data = deserialize<AppConfig>('app-config');
// ... do something with the app config
</script>
使用自定义序列器和解析器
如果您想要选择更复杂的数据处理, 可以带自定义序列器/ 参数 .
以下是数据序列化的例子 JSON.stringify 无法使用 Rich Harris' (例如日期或 BigInt) devalue数字 :
---
import {stringify} from 'devalue';
import Serialize from "@ayco/astro-resume";
const data = {
now: new Date(),
age: BigInt('3218378192378')
}
export type Data = typeof data;
---
<Serialize data={data} id="my-data" use={stringify} />
<script>
import {parse} from 'devalue';
import {deserialize} from '@ayco/astro-resume';
import type {Data} from './index.astro';
const {age, now} = deserialize<Data>('my-data', parse);
console.log(typeof age); // 'bigint'
console.log(now instanceof Date); // true
</script>
注意自定义序列器的输出是写在页面的正文上,所以它必须自己逃出——见下文. devalue 已经 如果没有的话, Serialize 拒绝提供而不是运送突围.
逃脱 & XSS
序列化数据写入于 a <script> 元素,所以字符串类似 </script><img src=x onerror=alert(1)> 在数据中,否则会提前关闭该元素,让其余部分以 HTML 运行.
Serialize 避免这一切 < 输出为 \u003c这是有效的JSON字符串逃脱,所以没有损失.. deserialize() 原弦还给你 </script 绝对不能出现在序列有效载荷中.
<Serialize id="my-data" data={{evil: '</script><img src=x onerror=alert(1)>'}} />
<!-- renders as: -->
<script type="application/json" id="my-data">{"evil":"\u003c/script>\u003cimg src=x onerror=alert(1)>"}</script>
有两个限制值得了解:
-
自定义序列器不会为你逃脱. 当你通过
use,它的返回值不作修改地写到页面上,这样一个发布自己格式的序列器就不会被损坏。 用一个逃跑的<本身 -devalue已经.作为后援,
Serialize如果串行输出仍然包含</script,因此一个无法逃避的序列器在渲染时会响亮地出故障,而不是运输XSS. 那张支票是定向的,不是HTML脚本的完整模型解析——一个能逃脱的序列器<仍然值得信赖. -
这能保护有效载荷,而不是你用它做什么. 数据你取出
deserialize()回到DOM 仍然需要正常的护理 更喜欢textContent结束innerHTML。 。 。 .
错误警告( W) deserialize()
这个 deserialize() 函数可以给出以下内容:
- 错误RR: 找不到匹配 - 没有
JSON带有指定标识的脚本 - ERR: 分析数据失败
<id>- 发现一个脚本,但是JSON.parse(或您的定制解析器) 扔在它的内容 - 警告: 多个匹配
<id>- 有多个JSON用同一ID找到的脚本
抛出的错误将潜在的失败保留为 cause因此,真正的问题仍然存在,以检查:
try {
const data = deserialize<Data>('my-data');
} catch (err) {
console.error(err.message); // astro-resume ERR: Failed to parse data for "my-data".
console.error(err.cause); // SyntaxError: Unexpected token ...
}
Serialize 在服务器上执行同样内容 : 如果您的数据无法被序列化, 则将“ 数据不可序列化 ” 与原始数据一起丢出 TypeError 圆形结构,或 BigInt 没有自定义序列器——作为 cause。 。 。 .
关于
这是一个快速而简单的模式,可以将序列化的信息嵌入到您的 HTML 中,并以客户端脚本提供. 这不需要客户端JS 直接将数据嵌入到您的HTML上.
这个 Serialize 组件会将数据写入 JSON 中 <script type="application/json"> 用于按住字符串的元素.
这个 deserialize() 函数然后可以解析用于客户端脚本的值字符串.
还有一种模式 在 Astro 文档中给出 来使用自定义元素 data- 适当保护组件范围的道具。 它要求客户方联署材料.
贸易业务
其他一些框架本身会为你管理序列化的信息和ID, 但我们无法在Astro访问,因为我们并没有真正将一个框架传送到浏览器.
这很好,也是理想的(在我看来),因为我们知道HTML是如何形成的,以及我们正在向用户运送什么. 换句话说,我们必须自己去处理事情.
你必须管理身份证(即确保身份证是独一无二的),并了解这些证件是否具有特殊性 deserialize() 函数将爬行整个文档,其性能成本将视您的 HTML 大小而定.
身份证本身可以是任何字符串. 无效的 CSS 标识符 - my.data, (中文(简体) ), ns:data[0], (中文(简体) ), 1data 被逃脱 CSS.escape 在检查之前,所以他们决心而不是默默地不匹配.
发展
此寄存器是一个 pnpm 工作空间 : package/ 是已发布的软件包,并且 demo/ 是一个消耗它的Astro网站.
pnpm install
pnpm dev # run the demo site
pnpm lint # eslint
pnpm test # vitest (happy-dom for deserialize, Astro Container API for Serialize)
pnpm check # astro check on the demo
pnpm release # bumpp: bump package/package.json, commit, tag, push
pnpm lint, (中文(简体) ), pnpm test 财务报告和审定财务报表 pnpm check 是三个命令 CI 运行.
升级到 v1
断裂变化: deserialize<T> 默认为 unknown 改为 any。 。 。 .
// v0 — `data` was `any`, so this compiled and checked nothing
const data = deserialize('my-data');
data.hello.toUpperCase();
// v1 — name the type
const data = deserialize<Data>('my-data');
data.hello.toUpperCase();
要求只移动价值——记录价值,将其交给别的东西——保持工作不受影响。 调用到结果需要类型参数或缩小检查。 现已输入自定义解析器 (serialized: string) => T 而不是返回 any,所以通过一个也给 T 某种可以推断的东西.
V1 中的其他所有内容都是向后兼容的 :
- 安全修补——即使没有类型错误也要升级的原因.
<以\u003c,因此数据无法再从<script>键。 v0完全没有逃脱,所以包含</script>可作为 HTML 运行 。 这改变了页面上的字节,而不是返回的值。 见 逃脱 & XSS。 。 。 . - 无法使用 CSS 标识符的ID 将使用
CSS.escape,因此它们现在匹配而不是扔出"找不到匹配". - 失败的解析器会抛出它自己命名ID的错误, 并带有解析器
SyntaxError作为cause,而不是光刻错误. - 错误来自
Serialize将原始错误带入cause. 此前,它作为第二个论点被通过:Error()并悄悄地放弃. - 这个
astro拓宽对等依赖^6改为>=4,因此软件包安装在Astro 4、5和6上.
路线图
见 TODO 跟踪器 已规划的工作项目.
报告问题
要报告问题或要求特性,请开罚单 GitHub 图像,或发送纯文本电子邮件给 ~ayoayco/[email protected]