first commit

This commit is contained in:
2025-10-22 01:33:45 +08:00
commit 2080fa3878
251 changed files with 47081 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# 架构
让我们从宏观视角审视区块编辑器及Gutenberg代码库的架构设计与用户体验原则。
## 编辑器
- [核心概念](/docs/explanations/architecture/key-concepts.md)
- [数据格式与数据流](/docs/explanations/architecture/data-flow.md)
- [实体与撤销/重做机制](/docs/explanations/architecture/entities.md)
- [全站编辑模板](/docs/explanations/architecture/full-site-editing-templates.md)
- [编辑器样式体系](/docs/explanations/architecture/styles.md)
- [性能优化](/docs/explanations/architecture/performance.md)
## Gutenberg代码库
- [模块化与WordPress包管理](/docs/explanations/architecture/modularity.md)
- [代码库目录结构解析](/docs/contributors/folder-structure.md)
- **已过时!** [为何选择Puppeteer作为端到端测试工具](/docs/explanations/architecture/automated-testing.md)

View File

@@ -0,0 +1,922 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"type": "rectangle",
"version": 69,
"versionNonce": 1371893668,
"isDeleted": false,
"id": "dE1I-EnEvkRXGzhdq1JtQ",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 460,
"y": 200,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 180,
"height": 100,
"seed": 1590887460,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "whcfsZ0hp0-cjJAqlj0ob",
"type": "arrow"
},
{
"id": "sL5M8MOmbWBPiIMWJPKAv",
"type": "arrow"
},
{
"id": "iOHR_Txg1Rt_gfJPeXqZg",
"type": "arrow"
},
{
"id": "yLz66yM1_qJAWngBli08H",
"type": "arrow"
}
],
"updated": 1643633406183
},
{
"type": "text",
"version": 153,
"versionNonce": 670281809,
"isDeleted": false,
"id": "5rFvoYoEiSaqNhwmlPjxf",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 500,
"y": 220,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 106,
"height": 51,
"seed": 1103877284,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643794034400,
"fontSize": 20,
"fontFamily": 1,
"text": "WordPress'\ntheme.json",
"baseline": 44,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "WordPress'\ntheme.json"
},
{
"type": "rectangle",
"version": 61,
"versionNonce": 47826468,
"isDeleted": false,
"id": "otWLW4l8pmq6prNMU4yVj",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 700,
"y": 200,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 160,
"height": 100,
"seed": 1273608996,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "yLz66yM1_qJAWngBli08H",
"type": "arrow"
}
],
"updated": 1643633405414
},
{
"type": "text",
"version": 128,
"versionNonce": 1069768604,
"isDeleted": false,
"id": "r1nDCG4qF6PJ6TXDA8D0W",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 720,
"y": 220,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 100,
"height": 51,
"seed": 217970972,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "yLz66yM1_qJAWngBli08H",
"type": "arrow"
}
],
"updated": 1643633404909,
"fontSize": 20,
"fontFamily": 1,
"text": "theme's\ntheme.json",
"baseline": 44,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "theme's\ntheme.json"
},
{
"type": "rectangle",
"version": 161,
"versionNonce": 516967708,
"isDeleted": false,
"id": "uyFiLHMSH6y7H-s3quAxV",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 920,
"y": 40,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 200,
"height": 80,
"seed": 412676132,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "rAP823QGa675R6VNN5_Fj",
"type": "arrow"
},
{
"id": "zqYpQBYVH8MUQ1gbny0DW",
"type": "arrow"
},
{
"id": "bZAzg4sYJCkziajbnNelc",
"type": "arrow"
},
{
"id": "Gf8rkiJMUzlf_neDMa8Uo",
"type": "arrow"
}
],
"updated": 1643633370772
},
{
"type": "text",
"version": 252,
"versionNonce": 788243108,
"isDeleted": false,
"id": "2fO2-BoHTfIU44g9j0n-S",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 940,
"y": 60,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 165,
"height": 51,
"seed": 231248924,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "Gf8rkiJMUzlf_neDMa8Uo",
"type": "arrow"
},
{
"id": "RXAV87dIe7h-TdMpPkj8G",
"type": "arrow"
}
],
"updated": 1643633370772,
"fontSize": 20,
"fontFamily": 1,
"text": "Global styles UI\nin site editor",
"baseline": 44,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Global styles UI\nin site editor"
},
{
"type": "rectangle",
"version": 85,
"versionNonce": 1702175071,
"isDeleted": false,
"id": "hewO7U2_RQB8vdU_-mPPB",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 580,
"y": 660,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 340,
"height": 100,
"seed": 726898972,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "ccK0gnrFParTub-zbOTR2",
"type": "arrow"
},
{
"id": "eP2wDSsiTc_n6dPEvV37C",
"type": "arrow"
}
],
"updated": 1643794081747
},
{
"type": "text",
"version": 117,
"versionNonce": 581323025,
"isDeleted": false,
"id": "eU8Kg0BNmsuuDOweR3g88",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 660,
"y": 700,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 215,
"height": 26,
"seed": 404952740,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643794068178,
"fontSize": 20,
"fontFamily": 1,
"text": "global-styles-inline-css",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "global-styles-inline-css"
},
{
"type": "text",
"version": 13,
"versionNonce": 1960935025,
"isDeleted": false,
"id": "CnDgoEZ_UKEJ24A9Y8p-H",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 597,
"y": 667,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 38,
"height": 26,
"seed": 2110409124,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643794076348,
"fontSize": 20,
"fontFamily": 1,
"text": "CSS",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "CSS"
},
{
"type": "rectangle",
"version": 134,
"versionNonce": 827573105,
"isDeleted": false,
"id": "ymhNrWoCwMcy97bV3wcKR",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 480,
"y": 440,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 600.0000000000001,
"height": 60,
"seed": 856798492,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "rAP823QGa675R6VNN5_Fj",
"type": "arrow"
},
{
"id": "zqYpQBYVH8MUQ1gbny0DW",
"type": "arrow"
},
{
"id": "whcfsZ0hp0-cjJAqlj0ob",
"type": "arrow"
},
{
"id": "bZAzg4sYJCkziajbnNelc",
"type": "arrow"
},
{
"id": "sL5M8MOmbWBPiIMWJPKAv",
"type": "arrow"
},
{
"id": "3BPPljmfDBwi9j0UFo72A",
"type": "arrow"
},
{
"id": "iOHR_Txg1Rt_gfJPeXqZg",
"type": "arrow"
},
{
"id": "Vb1dPpA_zGw4ibPc2H8XD",
"type": "arrow"
},
{
"id": "4EUXGzEm8Fy3BBuebotFc",
"type": "arrow"
},
{
"id": "celRUoUyUQIT-gGhAXyEK",
"type": "arrow"
},
{
"id": "yLz66yM1_qJAWngBli08H",
"type": "arrow"
}
],
"updated": 1643794050979
},
{
"type": "arrow",
"version": 761,
"versionNonce": 2121815743,
"isDeleted": false,
"id": "Vb1dPpA_zGw4ibPc2H8XD",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 779.6809331076444,
"y": 299.60856638674625,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 2.816506265121575,
"height": 132.78830939921954,
"seed": 1207657756,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643794051290,
"startBinding": null,
"endBinding": {
"elementId": "ymhNrWoCwMcy97bV3wcKR",
"gap": 7.603124214034267,
"focus": 0.010960154267448993
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
2.816506265121575,
132.78830939921954
]
]
},
{
"type": "line",
"version": 116,
"versionNonce": 2071091108,
"isDeleted": false,
"id": "uIhYulJsyst7aUpzTX4go",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 340.5942201212048,
"y": 361.19947067946197,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 916.0579537755439,
"height": 0,
"seed": 1575735580,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643633370772,
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null,
"points": [
[
0,
0
],
[
916.0579537755439,
0
]
]
},
{
"type": "text",
"version": 57,
"versionNonce": 1217366463,
"isDeleted": false,
"id": "siLfF4byWfGnkekw1DNmu",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 380,
"y": 40,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 68,
"height": 26,
"seed": 941782436,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643794093388,
"fontSize": 20,
"fontFamily": 1,
"text": "INPUT",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "INPUT"
},
{
"type": "line",
"version": 155,
"versionNonce": 571458340,
"isDeleted": false,
"id": "2EOVSOSu-XvcXmsarGRNX",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 340.31421109884974,
"y": 599.7727356031537,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 916.0579537755439,
"height": 0,
"seed": 482945060,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643633370772,
"startBinding": null,
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": null,
"points": [
[
0,
0
],
[
916.0579537755439,
0
]
]
},
{
"type": "text",
"version": 65,
"versionNonce": 438071580,
"isDeleted": false,
"id": "iqUBVRUA4jHj817dlF-oa",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 360,
"y": 640,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 89,
"height": 26,
"seed": 578631196,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643633370772,
"fontSize": 20,
"fontFamily": 1,
"text": "OUTPUT",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "OUTPUT"
},
{
"type": "text",
"version": 160,
"versionNonce": 2029239972,
"isDeleted": false,
"id": "2Zb3N-iXP0MEwBqCay37k",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 520,
"y": 460,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 505,
"height": 26,
"seed": 934136228,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643633370772,
"fontSize": 20,
"fontFamily": 1,
"text": "Internal Representation (WP_Theme_JSON object)",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Internal Representation (WP_Theme_JSON object)"
},
{
"type": "arrow",
"version": 694,
"versionNonce": 1024752273,
"isDeleted": false,
"id": "celRUoUyUQIT-gGhAXyEK",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 779.718628924312,
"y": 501,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 1.0791064091945373,
"height": 126.66251027234523,
"seed": 2100655268,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643794051290,
"startBinding": {
"elementId": "ymhNrWoCwMcy97bV3wcKR",
"gap": 1,
"focus": 0.0018167083707156541
},
"endBinding": null,
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
1.0791064091945373,
126.66251027234523
]
]
},
{
"type": "rectangle",
"version": 179,
"versionNonce": 1901269284,
"isDeleted": false,
"id": "4IJppZHOQRDTcVw-pjt1z",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 920,
"y": 200,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 180,
"height": 100,
"seed": 1157165980,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "rAP823QGa675R6VNN5_Fj",
"type": "arrow"
},
{
"id": "zqYpQBYVH8MUQ1gbny0DW",
"type": "arrow"
},
{
"id": "bZAzg4sYJCkziajbnNelc",
"type": "arrow"
},
{
"id": "3BPPljmfDBwi9j0UFo72A",
"type": "arrow"
},
{
"id": "4EUXGzEm8Fy3BBuebotFc",
"type": "arrow"
},
{
"id": "Gf8rkiJMUzlf_neDMa8Uo",
"type": "arrow"
},
{
"id": "RXAV87dIe7h-TdMpPkj8G",
"type": "arrow"
}
],
"updated": 1643633370772
},
{
"type": "text",
"version": 289,
"versionNonce": 880880164,
"isDeleted": false,
"id": "CA1UwOP4UrmE6pla-3RZT",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 960,
"y": 220,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 100,
"height": 51,
"seed": 1509336100,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [
{
"id": "RXAV87dIe7h-TdMpPkj8G",
"type": "arrow"
}
],
"updated": 1643633398374,
"fontSize": 20,
"fontFamily": 1,
"text": "user's\ntheme.json",
"baseline": 44,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "user's\ntheme.json"
},
{
"type": "text",
"version": 84,
"versionNonce": 773068836,
"isDeleted": false,
"id": "loyhrrk-dULHcklwEwSld",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "dotted",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 360,
"y": 380,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 118,
"height": 26,
"seed": 1415224092,
"groupIds": [],
"strokeSharpness": "sharp",
"boundElements": [],
"updated": 1643633370773,
"fontSize": 20,
"fontFamily": 1,
"text": "INTERNALS",
"baseline": 18,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "INTERNALS"
},
{
"type": "arrow",
"version": 878,
"versionNonce": 400369375,
"isDeleted": false,
"id": "4EUXGzEm8Fy3BBuebotFc",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1020.5557521929989,
"y": 301,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 0.8847309846728422,
"height": 129,
"seed": 1086156700,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643794051290,
"startBinding": {
"elementId": "4IJppZHOQRDTcVw-pjt1z",
"gap": 1,
"focus": -0.12071261013586085
},
"endBinding": {
"elementId": "ymhNrWoCwMcy97bV3wcKR",
"gap": 10.000000000000002,
"focus": 0.7974420373674143
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-0.8847309846728422,
129
]
]
},
{
"type": "arrow",
"version": 20,
"versionNonce": 39323804,
"isDeleted": false,
"id": "RXAV87dIe7h-TdMpPkj8G",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 1020.9070453807711,
"y": 120.28604858589358,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 0.9070453807711374,
"height": 59.71395141410642,
"seed": 748070564,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643633370773,
"startBinding": {
"elementId": "2fO2-BoHTfIU44g9j0n-S",
"focus": 0.012843458210903786,
"gap": 9.286048585893582
},
"endBinding": {
"elementId": "4IJppZHOQRDTcVw-pjt1z",
"focus": 0.09846585725336464,
"gap": 20
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
-0.9070453807711374,
59.71395141410642
]
]
},
{
"type": "arrow",
"version": 897,
"versionNonce": 44630129,
"isDeleted": false,
"id": "yLz66yM1_qJAWngBli08H",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"angle": 0,
"x": 540.1474954442242,
"y": 301,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"width": 3.013965627388984,
"height": 131.59259259259267,
"seed": 1185633948,
"groupIds": [],
"strokeSharpness": "round",
"boundElements": [],
"updated": 1643794051290,
"startBinding": {
"elementId": "dE1I-EnEvkRXGzhdq1JtQ",
"gap": 1,
"focus": 0.1209125431173364
},
"endBinding": {
"elementId": "ymhNrWoCwMcy97bV3wcKR",
"gap": 7.407407407407392,
"focus": -0.7848083884682261
},
"lastCommittedPoint": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"points": [
[
0,
0
],
[
3.013965627388984,
131.59259259259267
]
]
}
],
"appState": {
"gridSize": 20,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

View File

@@ -0,0 +1,20 @@
# 自动化测试
## 为何选择Puppeteer作为端到端测试工具
当前存在丰富的网络端到端自动化测试工具生态。因此常有人提出疑问:"为何Gutenberg选用[Puppeteer](https://developers.google.com/web/tools/puppeteer/)而非([Cypress](https://cypress.io/)、[Selenium](https://www.selenium.dev/)、[Playwright](https://github.com/microsoft/playwright)等)"考虑到端到端测试在构建结果方面存在历史性的不稳定性在评估工具提供的价值是否超过维护成本时这个问题显得尤为关键。虽然我们应始终保持重新评估早期决策的开放态度但无论是过去还是现在Puppeteer始终是端到端测试方案中的最佳折中选择。
其优势包括:
- **与现有测试框架的互操作性**。Puppeteer"仅"是一个控制Chrome浏览器的工具并不预设测试环境的集成方式。虽然这需要额外确保测试环境的可用性但也使其能够灵活适配现有配置。Gutenberg得以在单元测试和端到端测试中持续使用Jest框架。这与Cypress等其他方案形成鲜明对比——后者提供自成体系的测试框架和断言库作为一体化解决方案。
- **表达力强且可预测的API**。Puppeteer在底层浏览器行为访问与现代化JavaScript[异步/等待语法](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await)的命令发布/响应等待之间实现了精妙平衡。相较之下其他方案要么不支持原生异步功能要么未开放直接浏览器访问权限要么采用自定义领域特定语言来表达浏览器命令和断言。尽管Puppeteer主要针对Chrome浏览器在跨浏览器覆盖方面存在局限但有限的浏览器目标反而能提供更一致的测试结果并确保代码在浏览器环境中获得更可靠的评估。
- **暴露问题而非掩盖缺陷**。许多替代方案提供自动等待网络请求完成或页面元素异步加载的功能。虽然这为处理不可预知的延迟提供了便利,但也可能无意间掩盖真实存在的用户体验问题。例如,若某个元素仅在网络请求或计算完成后才会显示,开发者很容易忽略这些延迟可能导致用户遭遇不可预测的挫败体验([案例](https://github.com/WordPress/gutenberg/pull/11287)。鉴于开发者通常在高性能设备和稳定网络环境下测试低端设备或不稳定网络连接下的韧性考量往往被忽视。Puppeteer通过显式的`waitFor*`表达式迫使开发者正视这些延迟,使测试更贴近真实用户的体验场景。
- **调试功能**。当测试失败时必须具备直观的问题诊断与解决手段。虽然相比竞品功能较为基础但Puppeteer确实提供了"有头模式"(可见浏览器界面)和延迟操作等调试选项。结合其与原生语言/运行时功能(如调试器语句或断点)的良好互操作性,为开发者提供了充分的调试支持。
更多背景信息请参阅:
- [测试概览:端到端测试](/docs/contributors/code/testing-overview.md#端到端测试)
- [测试Puppeteer端到端测试实验](https://github.com/WordPress/gutenberg/pull/5618)
- 在早期迭代中贡献团队曾选择Cypress进行端到端测试。该拉取请求阐述了该方案存在的问题并提出了向Puppeteer迁移的初步建议。
- [JavaScript聊天纪要2020年1月28日](https://make.wordpress.org/core/2020/02/04/javascript-chat-summary-january-28-2020/)
- Playwright是由多位Puppeteer原始贡献者打造的新方案。它提供更广泛的浏览器覆盖和更可靠的测试表现。尽管撰写本文时该工具尚处早期开发阶段但已引起将其作为未来端到端测试方案的评估兴趣。

View File

@@ -0,0 +1,125 @@
### 分隔符与解析表达式语法
我们最终选择尝试寻找一种方法在保留现有HTML语法规范性、明确性和无歧义性的基础上进行创新。在HTML体系内我们拥有多种可行方案。
在这些方案中有人提出了一种新颖思路通过将数据存储在HTML注释中既能确保不破坏文档中原有的HTML结构又能保证浏览器会忽略这些内容同时还能简化文档解析流程。
HTML注释的独特之处在于它们不会合法存在于模糊位置——比如像`<img alt='data-id="14"'>`这样的HTML属性内部。注释语法还具有高度包容性解析HTML属性需要复杂处理而注释规则却极其简单——以`<!--`起始,中间可包含除`-->`外的任意内容,直至遇到首个`-->`结束。这种简洁性与包容性意味着解析器可以通过多种方式实现且无需深入理解HTML语法我们还能在注释中自由使用更便捷的语法——仅需转义双连字符序列即可。我们在存储块属性时充分利用了这一特性将JSON字面量直接嵌入注释。
经过解析器处理后我们获得了可直接操作的标准对象无需担心数据转义或反转义问题——序列化过程已自动处理这些细节。由于注释与其他HTML标签存在显著差异加之我们可以通过首轮解析提取顶层块实际上并不需要完全有效的HTML文档
这对解析器的简洁性与性能表现具有重大意义。这些明确的分界符还能防止单个块的损坏蔓延至其他块或污染整个文档。系统也得以在渲染前识别出未知块。
*注:* 块的核心特征在于其语义与提供的隔离机制即其身份标识。而数据存储位置则具有更高灵活性。块不仅支持静态本地数据通过HTML注释中的JSON字面量或块内HTML实现未来还将支持更多机制例如全局块或辅助存储于`WP_Post`对象)。详见[属性说明](/docs/reference-guides/block-api/block-attributes.md)。
### 序列化块的解剖结构
当块在编辑会话结束后保存至内容时,其属性会根据块特性被序列化为这些显式注释分隔符:
```html
<!-- wp:image -->
<figure class="wp-block-image"><img src="source.jpg" alt="" /></figure>
<!-- /wp:image -->
```
对于需服务器预渲染的纯动态块,其形态可能如下:
```html
<!-- wp:latest-posts {"postsToShow":4,"displayPostDate":true} /-->
```
## 数据生命周期
总而言之,区块编辑器工作流通过词法分隔符辅助,将已保存文档解析为内存中的块树结构。编辑过程中的所有操作都在块树内部进行,最终通过将块序列化回`post_content`完成流程。
该工作流依赖序列化/解析器组合来实现文章持久化。理论上文章数据结构既可通过插件存储也可从远程JSON文件获取后转换为块树结构。
# 数据流与数据格式
## 格式规范
区块编辑器文章是具备区块感知能力的文章表征形式:由一系列语义一致的描述构成,阐明每个区块的定义及其核心数据。这种表征仅存在于内存中,如同排版工坊中的[活字追排](https://zh.wikipedia.org/wiki/%E6%8E%92%E7%89%88#%E6%B4%BB%E5%AD%97%E6%8E%92%E7%89%88),随着[字模](https://zh.wikipedia.org/wiki/%E5%AD%97%E6%A8%A1)的嵌入与重新定位而持续变化。
区块编辑器文章并非其最终产物——即`post_content`(文章内容)。后者如同印刷成品,为读者优化呈现的同时,仍保留着用于后续编辑的隐形标记。
区块编辑器的输入与输出采用当前格式的区块对象树:
```js
const value = [ block1, block2, block3 ];
```
### 区块对象
每个区块对象包含唯一标识符、属性集合及可能的子区块列表。
```js
const block = {
clientId, // 唯一字符串标识符
type, // 区块类型(段落、图片等)
attributes, // 代表当前区块直接属性/内容的键值对集合
innerBlocks, // 子区块或内部区块数组
};
```
需注意属性键名与类型、允许嵌套的区块均由区块类型定义。例如核心引用区块包含字符串类型的`cite`属性表示引用来源,而标题区块则包含数值型`level`属性表示标题层级1至6级
在编辑器的区块生命周期中,区块对象可接收额外元数据:
- `isValid`:布尔值,标识区块是否有效
- `originalContent`区块原始HTML序列化内容
**示例**
```js
// 简单段落区块
const paragraphBlock1 = {
clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a3',
type: 'core/paragraph',
attributes: {
content: '这是段落区块的<strong>内容</strong>',
dropCap: true,
},
};
// 分隔符区块
const separatorBlock = {
clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a4',
type: 'core/separator',
attributes: {},
};
// 包含双栏段落区块的列区块
const columnsBlock = {
clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a7',
type: 'core/columns',
attributes: {},
innerBlocks: [
{
clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a5',
type: 'core/column',
attributes: {},
innerBlocks: [ paragraphBlock1 ],
},
{
clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a6',
type: 'core/column',
attributes: {},
innerBlocks: [ paragraphBlock2 ],
},
],
};
```
## 序列化与解析
![流程图](https://docs.google.com/drawings/d/1iuownt5etcih7rMMvPvh0Mny8zUA1Z28saxjxaWmfJ0/pub?w=1234&h=453)
需要注意的是,这种数据模型仅在文章编辑过程中存在于内存中。页面最终渲染时对浏览者不可见,正如印刷成品不会显露印刷机上字母的组成结构。
鉴于整个WordPress生态在渲染或编辑文章时期望接收HTML格式区块编辑器通过序列化将其数据转换为可保存至`post_content`的形式。这确保了内容存在单一可信源且该源始终保持可读性并与当前所有WordPress内容交互工具兼容。若将对象树单独存储我们将面临`post_content`与对象树失步的风险,以及数据在两地重复存储的问题。
因此序列化过程使用HTML注释作为显式区块定界符其中可包含非HTML格式的属性将区块树转换为HTML。这一过程如同在印刷页面上留下隐形标记为原始结构化意图保留痕迹。
这是流程的一端。另一端则关乎如何在下一次编辑时重建区块集合。正如通过基本规则定义如何将树转换为类HTML字符串形式化语法定义了应如何加载区块编辑器文章的序列化表征。区块编辑器文章并非为手动编辑而设计亦非作为HTML文档进行编辑——因为其本质并非HTML。
它们只是恰巧以无需传统系统转换即可直接查看的方式存储在`post_content`中。诚然在没有相应机制的情况下将存储的HTML加载至浏览器可能会影响体验若包含动态内容区块动态元素可能无法加载服务器生成的内容可能无法显示交互内容可能保持静态。但至少这确保了在未启用区块功能的主题和环境中仍可查看区块编辑器文章并提供了最便捷的内容访问方式。换言之即使保存的HTML按原样渲染文章内容仍能基本保持完整。

View File

@@ -0,0 +1,70 @@
# 实体与撤销/重做
无论是文章编辑器还是站点编辑器WordPress 编辑器操作的都是我们称为实体记录的对象。这些对象代表着文章、页面、用户、分类项、模板等数据。它们既是存储在数据库中的数据,也是被编辑器操作的对象。每个编辑器都能同时获取、编辑和保存多个实体记录。
例如在站点编辑器中打开页面时:
- 您可以编辑页面本身的属性(标题、内容等)
- 您可以编辑页面模板的属性(模板内容、设计等)
- 您可以编辑模板所使用的模板部件属性(页眉、页脚等)
编辑器会追踪所有这些修改,并协调所有已修改记录的保存操作。这些功能都在 `@wordpress/core-data` 包中实现。
## 编辑实体
要编辑实体,首先需要获取并将其加载到 `core-data` 存储中。例如以下代码将 ID 为 1 的文章加载到存储中(实体是文章,文章 1 是实体记录)。
````js
wp.data.select( 'core' ).getEntityRecord( 'postType', 'post', 1 );
````
实体加载后即可进行编辑。例如以下代码将文章标题设置为 "Hello World"。对于每个获取的实体记录,`core-data` 存储会追踪以下内容:
- **“持久化”记录**:从后端获取时记录的最终状态
- **“编辑”列表**:记录一个或多个属性的未保存本地修改
该程序包还提供了一系列操作来管理已获取的实体记录。
要编辑实体记录,可以调用 `editEntityRecord` 方法,该方法需要传入实体类型、实体 ID 和新实体记录作为参数。以下示例将 ID 为 1 的文章标题设置为 "Hello World"。
````js
wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { title: 'Hello World' } );
````
编辑实体记录后即可进行保存。以下代码保存了 ID 为 1 的文章。
````js
wp.data.dispatch( 'core' ).saveEditedEntityRecord( 'postType', 'post', 1 );
````
## 撤销/重做
由于 WordPress 编辑器允许同时编辑多个实体记录,`core-data` 包会在公共的撤销/重做堆栈中追踪所有已获取和编辑的实体记录。撤销/重做堆栈中的每个步骤都包含一个“编辑”列表,在调用 `undo` 或 `redo` 操作时需要同时执行这些编辑。
为了正确执行撤销和重做操作,每个编辑列表中的修改都包含以下信息:
- **实体种类与名称**core-data 中的每个实体都由_(种类, 名称)_对标识对应被修改实体的标识符
- **实体记录 ID**:被修改记录的 ID
- **属性**:被修改属性的名称
- **原值**:属性的原始值(用于执行撤销操作)
- **新值**:属性的新值(用于执行重做操作)
例如,假设用户先编辑了文章标题,接着修改了文章别名,然后又修改了文章中使用的可重用块标题。撤销/重做堆栈中将存储以下信息:
- `[ { kind: 'postType', name: 'post', id: 1, property: 'title', from: '', to: 'Hello World' } ]`
- `[ { kind: 'postType', name: 'post', id: 1, property: 'slug', from: 'Previous slug', to: 'This is the slug of the hello world post' } ]`
- `[ { kind: 'postType', name: 'wp_block', id: 2, property: 'title', from: 'Reusable Block', to: 'Awesome Reusable Block' } ]`
存储还会维护一个指向当前“撤销/重做”步骤的“指针”。默认情况下,指针始终指向堆栈中的最后一个项目。当用户执行撤销或重做操作时,该指针会相应更新。
### 缓存变更
撤销/重做的核心行为还支持所谓的“缓存修改”。这些修改不会立即存储在撤销/重做堆栈中。例如当用户在文本字段中输入时,字段值会在存储中修改,但该修改只有在用户移动到下一个单词或经过几毫秒后才会存入撤销/重做堆栈。这样做是为了避免为用户输入的每个字符都创建新的撤销/重做步骤。
缓存变更会被保留在撤销/重做堆栈之外的“修改缓存”中,只有当显式调用 `__unstableCreateUndoLevel` 或当下一个修改不是缓存修改时,这些修改才会存入撤销/重做堆栈。
默认情况下,所有对 `editEntityRecord` 的调用都被视为“非缓存”操作,除非传入 `isCached` 选项为 true。例如
```js
wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { title: 'Hello World' }, { isCached: true } );
```

View File

@@ -0,0 +1,44 @@
# 站点编辑模板
## 模板与模板部件流程
本文档将阐述模板及模板部件在前端如何渲染、在后端如何编辑的内部机制。
## 存储机制
与常规模板类似,区块模板最初以文件形式存在于主题文件夹中,但主要区别在于用户可通过站点编辑器的界面编辑这些模板。
当用户编辑模板(或模板部件)时,初始的主题模板文件将保持原样,但模板的衍生版本会保存至`wp_template`自定义文章类型(模板部件则保存至`wp_template_part`)。
这意味着在任意时间点网站前端渲染会同时使用主题文件中的模板文件与CPT模板经编辑的模板
## 同步机制
为简化从两个不同位置编辑和渲染模板的算法,我们执行了名为"模板同步"的操作。
同步操作包括将主题模板复制到`wp_template`(及`wp_template_part`)自定义模板中并标记为`auto-draft`状态。当用户编辑这些模板时,状态将更新为`publish`
这意味着:
- 模板的渲染/获取只需考虑自定义文章类型模板无需直接从主题文件夹获取模板文件。同步机制将确保这些模板被复制到CPT中
- 未编辑的主题模板保持`auto-draft`状态
- 经编辑的主题模板具有`publish`状态
同步机制对以下两个流程至关重要:
- 当编辑模板和模板部件时站点编辑器前端通过REST API获取已编辑和可用的模板。这意味着所有对`wp-templates``wp-template-parts`端点的`GET`API请求都需要进行同步
- 当渲染模板(有时称为"解析模板"这是WordPress遵循的算法用于遍历模板层级结构并为当前加载页面找到正确的渲染模板
- 当导出区块主题时我们需要将所有模板重新导出为文件。同步机制可简化此操作仅导出CPT模板
## 主题切换
由于区块主题使用的模板可以相互引用,并且可以保存到自定义文章类型中,因此可能混合使用来自不同主题的模板和模板部件。例如:
- 用户可能喜欢主题A的"页眉"模板部件并希望在主题B中使用它
- 用户可能喜欢主题A的"联系"模板并希望在主题B中使用它
实现这些流程需要精心设计的界面和用户体验。在全站编辑的当前阶段,我们首先禁止这些可能性,确保模板和模板部件具有主题专属性。
尽管如此记录模板和模板部件的初始来源基于哪个主题仍然非常重要。我们通过为每个模板和模板部件CPT条目保存包含主题标识符的`theme`文章元数据来实现这一点。
未来,我们可能会考虑允许用户混合使用具有不同`theme`文章元数据值的模板和模板部件。

View File

@@ -0,0 +1,69 @@
# 核心概念
## 区块
区块是用于构建和交互内容的抽象单元。当它们组合在一起时,就构成了网页的内容。从段落、视频到网站标题,所有内容都以区块形式呈现。
区块形式多样但提供统一的交互界面。它们可被插入、移动、重新排序、复制、克隆、转换、删除、拖拽和组合。区块还支持复用功能,可在不同文章和文章类型间共享,或在同一文章中多次使用。简单来说,可将区块视为更优雅的简码,配备丰富的格式工具供用户编排内容。
区块设置与内容主要在三个区域进行自定义:区块画布、区块工具栏和区块检查器。
### 组合性
区块支持多种组合方式。它们具有层级结构,允许区块嵌套于其他区块内。嵌套区块与其容器分别称为*子区块*和*父区块*。例如*多栏*区块可作为父区块在其各栏中包含多个子区块。管理子区块使用的API命名为`InnerBlocks`
### 数据与属性
区块将内容解析为属性并可序列化为HTML。为此专门设计了新的区块语法规则区块语法本质上是HTML注释可以是自闭合标签或包含起始/结束标签。根据区块类型和用户自定义设置主标签内可能包含JSON对象。这种原始区块形态称为序列化形态。
```html
<!-- wp:paragraph {"key": "value"} -->
<p>欢迎来到区块世界。</p>
<!-- /wp:paragraph -->
```
区块分为静态与动态两类。静态区块包含渲染内容和属性对象,可根据变更重新渲染。动态区块在生成文章内容时需要服务器端数据和渲染过程。
每个区块包含属性或配置设置这些数据可从内容中的原始HTML、元数据或其他自定义来源获取。
详细了解[数据格式与数据流](/docs/explanations/architecture/data-flow.md)。
### 区块转换
区块具备转换为其他区块类型的能力。既可实现基础操作(如将段落转为标题),也能完成复杂转换(如多张图片转为相册)。区块转换适用于单个区块及多选区块组合,内部区块变体也可作为转换目标。
### 区块变体
区块变体是特定区块类型的预定义初始属性集合。该API支持创建基础区块并衍生出多种配置方案。变体提供不同的交互界面既可在库中显示为全新区块也可作为插入新区块时的预设模板。详见[API文档](/docs/reference-guides/block-api/block-registration.md#variations-optional)。
**区块进阶指南**
- **[区块API](/docs/reference-guides/block-api/README.md)**
- **[教程:构建自定义区块](/docs/getting-started/devenv/get-started-with-create-block.md)**
## 可复用区块
可复用区块是区块(或多个区块组合)的**实例**,可在多处插入和编辑,并保持全局同步。当在某处编辑可复用区块时,所有使用该区块的文章和页面都会同步更新。典型应用场景包括:具有特定内容和自定义颜色的标题区块(用于多个页面),以及侧边栏小组件(用于所有页面)。
对可复用区块的任何编辑都会自动同步到所有使用位置,避免在不同文章中重复修改。
技术上,可复用区块以隐藏文章类型(`wp_block`)存储,属于动态区块,通过"引用"方式关联`post_id`并返回对应区块的`post_content`
## 区块模式
[区块模式](/docs/reference-guides/block-api/block-patterns.md)是由多个区块组合形成的设计范式。这些设计模式为快速构建高级页面和布局提供起点,无需逐个插入区块。区块模式小至单个区块,大至整页内容。与可复用区块不同,模式插入后不会与原始内容保持同步,其包含的区块需用户自行编辑定制。本质上,模式就是常规区块的组合。主题可通过注册模式,为用户提供符合其设计语言的快速入门方案。
## 模板
文章编辑器专注于内容创作,而[模板](/docs/reference-guides/block-api/block-templates.md)编辑器支持用区块声明和编辑整个站点(从页眉到页脚)。模板系统分为完整页面模板和模板部件(描述模板内的可复用区域,包括页眉、侧边栏、页脚等语义区域)。
这些模板和模板部件可由主题组合注册。用户也可通过区块编辑器完全自定义它们。在编辑模板时,与站点属性(如站点标题、描述、徽标、导航等)交互的区块集合尤为实用。自定义模板保存在`wp_template`文章类型中包括静态页面和动态页面如归档页、单篇文章页、首页、404页等
注意:自定义文章类型也可通过初始`post_content`模板初始化,请勿与上述主题模板系统混淆。
深入了解[全站编辑模板](/docs/explanations/architecture/full-site-editing-templates.md)。
## 样式
样式系统(代码中仍沿用旧称“全局样式”)既是用户通过编辑器访问的界面,也是通过[`theme.json`文件](/docs/how-to-guides/themes/global-settings-and-styles.md)实现的配置系统。该文件整合了通常分散在多个`add_theme_support`调用中的配置项简化与编辑器的通信。其目标是改进以下功能的声明方式应启用的设置、主题提供的特定工具如自定义调色板、可用的设计工具以及协调WordPress、活跃主题和用户样式的基础架构。
了解更多关于[全局样式](/docs/explanations/architecture/styles.md#global-styles)的信息。

View File

@@ -0,0 +1,103 @@
## 进阶阅读
- [软件包参考](/docs/reference-guides/packages.md)
# 模块化设计
WordPress 区块编辑器的核心理念在于通过组合独立区块来撰写文章或构建页面。区块之间能够相互调用与交互,这种设计赋予了系统高度的模块化特性与灵活性。
但区块编辑器的模块化不仅体现在功能与输出层面。古腾堡项目仓库本身也是由多个可复用独立模块(即程序包)构建而成,这些模块共同构成了我们熟悉的应用程序与交互界面。这些模块被称为 [WordPress 程序包](https://www.npmjs.com/org/wordpress),会定期发布并更新至 npm 包仓库。
这些程序包不仅为区块编辑器提供核心支持,也可用于增强 WordPress 管理后台或外部环境的任何页面。
## 设计理念
采用模块化架构为所有参与者带来多重优势:
- 每个程序包都是独立单元,拥有明确定义的公共 API 用于与其他程序包及第三方代码交互。这使得**核心贡献者**能更清晰地理解代码库,可专注于单个程序包的开发维护,并在明确知晓变更影响范围的前提下进行更新
- 模块化架构对**终端用户**同样有益。通过在不同管理页面选择性加载脚本,有效控制资源包体积。例如当使用组件包构建插件设置页面时,就无需额外加载区块编辑器包
- 这种架构允许**第三方开发者**通过 npm 或 WordPress 脚本依赖的方式,在 WordPress 内外环境中复用这些程序包
## 程序包分类
古腾堡仓库中的几乎所有功能都被构建为程序包。这些程序包可分为两大类型:
### 生产环境包
这类程序包以 JavaScript 脚本形式随 WordPress 核心发布,构成在浏览器中运行的实际生产代码。例如:
- `components` 程序包提供可复用的 React 组件集合,用于快速原型设计与界面构建
- `api-fetch` 程序包可用于调用 WordPress REST API
第三方开发者可通过两种方式使用这些生产环境包:
- 若在 WordPress 环境外构建 JavaScript 应用、网站或页面,可像使用常规 npm 包那样调用:
```
npm install @wordpress/components
```
```js
import { Button } from '@wordpress/components';
function MyApp() {
return <Button>精美按钮</Button>;
}
```
- 若开发 WordPress 插件,建议直接调用 WordPress 内置程序包。这样可实现多插件共享同一程序包,避免代码重复。在 WordPress 中,这些程序包以后缀为 `wp-包名称`(如 `wp-components`)的脚本句柄形式提供。只需将脚本添加到插件依赖中,即可通过全局变量 `wp` 调用:
```php
// myplugin.php
// 注册依赖 "components" 和 "element" 程序包的示例
wp_register_script( 'myscript', 'pathtomyscript.js', array ('wp-components', "react" ) );
```
```js
// 在脚本中使用程序包
const { Button } = wp.components;
function MyApp() {
return <Button>精美按钮</Button>;
}
```
手动定义脚本依赖对开发者而言可能繁琐易错。若需了解如何自动化此流程,请参阅 [@wordpress/scripts](https://developer.wordpress.org/block-editor/packages/packages-scripts/#build) 与 [@wordpress/dependency-extraction-webpack-plugin](https://developer.wordpress.org/block-editor/packages/packages-dependency-extraction-webpack-plugin/) 文档。
#### 包含样式文件的功能包
部分生产环境功能包需依赖样式文件才能正常运行。
- 若通过 npm 依赖形式使用功能包,样式文件将存放于功能包的 `build-style` 目录。请确保在应用中加载该样式文件。
- 若在 WordPress 环境中使用,需通过 wp_enqueue_style 函数加载样式文件,或将其添加至现有样式依赖中。样式文件句柄命名规则与脚本句柄保持一致。
在现有 WordPress 页面环境中,若未正确定义脚本或样式依赖,当这些资源已被 WordPress 或其他插件加载时,您的插件仍可能正常运行。但为规避未来版本可能出现的兼容性问题,强烈建议完整定义所有依赖项。
#### 包含数据存储的功能包
部分 WordPress 生产环境功能包通过数据存储机制管理状态。这些存储空间也可被第三方插件和主题调用,用于数据获取与操作。数据存储命名遵循 `core/功能包名称` 的标准化格式(例如 `@wordpress/block-editor` 功能包定义并使用 `core/block-editor` 数据存储)。
若在插件中通过此类存储空间访问或操作 WordPress 数据,请务必将对应的 WordPress 脚本添加至插件脚本依赖中以确保正常运行(例如:若从 `core/block-editor` 存储获取数据,应按前文示例将 `wp-block-editor` 包添加至脚本依赖)。
### 开发环境功能包
此类功能包用于开发模式,协助开发者完成 JavaScript 应用、WordPress 插件及主题的开发、构建和发布等日常任务。包含代码规范检查、项目构建、测试验证等开发工具。
## 编辑器功能包
![文章编辑器模块架构](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/explanations/architecture/assets/modules.png)
### 不同编辑器功能包有何区别?各自承担什么职责?
新贡献者往往会对文章编辑器由三个独立功能包(`@wordpress/edit-post``@wordpress/editor``@wordpress/block-editor`)分层构建的架构感到惊讶。
前文[设计初衷](#why)章节已说明单个功能包如何满足特定需求的设计理念,这同样适用于以下功能包:
- `@wordpress/block-editor` 提供用于实现区块编辑器的组件其操作对象为区块对象数组的基础数据。该包不预设数据保存方式且无需感知或依赖WordPress 环境。
- `@wordpress/editor` 是 WordPress 文章专用的增强版区块编辑器。它在 `@wordpress/block-editor` 组件基础上构建,具备 WordPress 文章概念感知能力,将表征区块的数据加载保存机制与文章及其内容相关联。同时提供在编辑器环境中处理文章对象的相关组件(如文章标题输入组件)。该功能包支持编辑任何文章类型的文章,且不限定渲染环境必须位于特定 WordPress 界面或布局中。
- `@wordpress/edit-post` 是 WordPress 后台“新建文章”(“编辑文章”)界面的具体实现。它负责对 `@wordpress/editor``@wordpress/block-editor` 提供的各类组件进行布局编排,完全掌控其在 WordPress 管理后台特定界面中的呈现方式。
通过这样的结构设计,这些功能包可在“新建文章”界面之外实现多样化组合应用:
- `@wordpress/edit-site``@wordpress/edit-widgets` 功能包可分别实现“站点编辑器”或“小工具编辑器”,其实现方式与 `@wordpress/edit-post` 高度相似。
- `@wordpress/editor` 可应用于“可重用区块”功能的实现,因其本质上是与 `wp_block` 文章类型关联的嵌套区块编辑器。
- `@wordpress/block-editor` 可独立于 WordPress 环境使用,或采用完全不同的保存机制。例如:可用于构建网站文章的评论区编辑器。

View File

@@ -0,0 +1,97 @@
# 性能表现
性能是编辑器应用的关键特性,块编辑器也不例外。
## 指标衡量
为确保块编辑器在版本迭代和开发过程中始终保持高性能,我们通过[性能基准测试任务](#性能基准测试任务)监控以下核心指标:
部分关键指标包括:
- **加载时间:** 编辑器页面加载所需时长。涵盖服务器响应时间、首次绘制时间、首次内容绘制时间、DOM内容加载完成时间、页面完全加载时间以及首区块渲染时间文章与站点编辑场景均包含
- **输入响应时间:** 在编辑器中输入时浏览器作出响应所需时长。
- **区块选择时间:** 用户选择区块后浏览器作出响应所需时长(插入区块等效于选择区块,监控选择操作即可同步覆盖这两项指标)。
## 关键性能决策与解决方案
**数据模块异步模式**
WordPress组件库与块编辑器的数据模块基于Redux构建。这意味着状态全局存储当状态发生变化时依赖该状态的组件UI会相应更新。
随着渲染组件数量增加例如长文编辑时由于全局状态会向所有组件分发事件性能将受到影响。这是Redux应用的常见问题Gutenberg通过数据模块异步模式解决了这一难题。
异步模式的核心在于:开发者可自主决定以同步或异步方式刷新/重渲染React组件树的特定部分。
在此语境下异步渲染意味着当全局状态触发变更时订阅者组件不会立即被同步调用而是等待浏览器进入空闲状态后再执行React树更新。
基于**编辑特定区块时,该区块的更新极少影响内容其他部分**这一理念,块编辑器画布仅以同步模式渲染当前选中区块,其余所有区块均采用异步渲染。这确保了随着文章内容增长,编辑器仍能保持流畅响应。
## 性能基准测试任务
我们提供了跨分支/标签/提交进行性能对比的工具。可通过以下方式本地运行:`./bin/plugin/cli.js perf [分支名]`,例如:
```
./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0
```
为获得最精确结果运行测试时必须确保测试版本与环境主题等完全一致各分支间唯一差异应为Gutenberg插件版本或用于构建插件的分支
为实现该目标,指令会先创建以下目录结构:
├── tests/packages/e2e-tests/specs/performance/*
│ 待运行的实际性能测试
├── tests/test/emptytheme
│ 测试环境所用主题(站点编辑器)
│── envs/branch1/.wp-env.json
│ branch1的wp-env配置文件除插件目录外与其他分支配置相同
│── envs/branch1/plugin
│ branch1的Gutenberg插件构建副本通过git checkout branch1获取
└── envs/branchX
所有其他分支均复制perf-envs/branch1的目录结构
完成目录准备后,性能测试指令将循环执行各性能测试套件(文章编辑器与站点编辑器),并执行以下操作:
1. 启动`branch1`对应环境
2. 运行当前套件的性能测试
3. 停止`branch1`对应环境
4. 对其余所有分支重复前三步操作
5. 计算当前套件所有性能指标的中位数值
所有测试套件执行完毕后,将生成汇总报告。
## 通过CodeVitals追踪性能
每次提交的性能结果将推送至codevitals可在[Gutenberg数据看板](https://www.codevitals.run/project/gutenberg)查看。通过趋势图可追踪特定指标随时间的变化情况。
因此确保所计算指标的稳定性至关重要。这意味着在代码与环境相同的情况下,重复运行测试应获得相近结果。
由于性能任务运行于GitHub CI环境我们不能完全相信两次相似任务运行获得的数值一致性。例如GitHub CI可能随时间推移分配不同的CPU与内存资源。为缓解此问题每次在trunk分支运行性能任务时我们会将当前提交的性能与固定的参考提交哈希进行对比这样无论环境如何变化都能持续追踪当前提交与参考提交间的相对差异。
### 更新参考提交
Gutenberg仅支持两个WP版本这对性能任务产生两方面影响
- 当Gutenberg支持的最低版本变更时用于运行性能任务的基础WP版本需同步更新。为此我们依赖插件`readme.txt`文件中的`Tested up to`标识。每次该标识变更时,性能任务所用版本也会相应调整。
- 更新性能任务使用的WP版本意味着用于保证性能测试稳定性的参考提交很可能与当前WP版本不兼容。因此每次`readme.txt`中的`Tested up to`标识更新时,必须同步更新`.github/workflows/performance.yml`中使用的参考提交。
新选的参考提交哈希需满足以下要求:
-`Tested up to`标识使用的新WP版本兼容
- 已在codevitals.run平台追踪所有现有指标
当发布涉及最低WordPress版本要求变更的插件更新时需更新Core SVN中失去支持分支的端到端测试GitHub Action工作流否则该分支在发布后的首次工作流运行将会失败。
可通过在测试矩阵中添加`gutenberg-version`输入来固定工作流中使用的插件版本。[Core-59221](https://core.trac.wordpress.org/changeset/59221)展示了6.4分支的此类变更示例。
**注意:** 请始终使用包含错误修复的最终发行版(例如`x.y.2``x.y.3`)。若最终发行版尚未确定,请创建[Trac工单](https://core.trac.wordpress.org/ticket/62488)以免遗忘。
**选择提交的简易方法是选取trunk分支上最近一次通过性能测试的提交。**
## 拓展阅读
- [高性能编辑器的演进之路](https://riad.blog/2020/02/14/a-journey-towards-a-performant-web-editor/)

View File

@@ -0,0 +1,549 @@
#### 语义化类名
当前正在扩展布局区块支持输出的稳定语义化类名,相关讨论可在[此议题](https://github.com/WordPress/gutenberg/issues/38719)中查看。
目前通过布局区块支持可输出的语义化类名包括:
- `is-layout-flow`:采用默认/流式布局类型的区块
- `is-layout-constrained`:采用约束布局类型的区块
- `is-layout-flex`:采用弹性布局类型的区块
- `is-layout-grid`:采用网格布局类型的区块
- `wp-container-$id`:其中`$id`为半随机数。该容器类仅当区块包含非默认布局值时存在请勿直接将其用于CSS选择器因其可能动态变化
- `is-horizontal`:当区块显式设置`orientation``horizontal`
- `is-vertical`:当区块显式设置`orientation``vertical`
- `is-content-justification-left`:当区块显式设置`justifyContent``left`
- `is-content-justification-center`:当区块显式设置`justifyContent``center`
- `is-content-justification-right`:当区块显式设置`justifyContent``right`
- `is-content-justification-space-between`:当区块显式设置`justifyContent``space-between`
- `is-nowrap`:当区块显式设置`flexWrap``nowrap`
### 禁用自动生成的布局样式
由于核心结构区块需要依赖布局样式,系统默认开启布局样式输出。但主题可通过使用`disable-layout-styles`区块支持来禁用自动生成的区块布局样式,同时保留语义化类名输出。选择此方案的主题需自行提供完整的布局样式支持,具体请参阅[主题支持文档](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-support/#disabling-base-layout-styles)中的相关说明。
### 基础布局样式
基础布局样式是指那些选择特定布局类型的所有区块共通的样式。常见的基础布局样式示例包括:为采用弹性布局类型的区块(例如按钮区块和社交图标区块)设置 `display: flex`,以及为受限布局提供默认的最大宽度。
基础布局样式通过[处理全局样式的主要 PHP 类](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/class-wp-theme-json.php)输出,并构成全局样式表的一部分。为了在经典主题中支持核心区块,无论主题是否提供自身的 `theme.json` 文件,这些样式始终会被输出。
通用布局定义存储在[核心布局区块支持文件](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php)中。
### 独立布局样式
当选择支持布局的区块被渲染时,会通过 [`layout.php`](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php) 处理并将以下两项内容添加到输出中:
- 语义类名会被添加到区块标记中,以指示正在使用的布局设置。例如,`is-layout-flow` 用于使用默认/流式布局的区块(如群组区块),而 `is-content-justification-right` 会在用户将区块设置为右对齐时添加。
- 为正在渲染的单个区块上设置的非默认布局值生成独立样式。这些样式通过容器类名附加到区块上,类名格式为 `wp-container-$id`,其中 `$id` 是一个[唯一编号](https://developer.wordpress.org/reference/functions/wp_unique_id/)。
### 可用的布局类型
目前有四种布局类型在使用:
- 默认/流式布局:项目垂直堆叠。父容器区块的显示值未指定,因此可以使用该 HTML 元素的默认值。对于大多数元素,这通常是 `block`。子元素之间的间距通过垂直边距处理。
- 受限布局:项目垂直堆叠,使用与流式布局相同的间距逻辑。具有子内容宽度的限制,为标准内容尺寸和宽尺寸输出宽度。默认使用 `theme.json``settings.layout` 设置的全局 `contentSize``wideSize` 值。
- 弹性布局:项目使用弹性盒子布局显示。默认为水平方向。子元素之间的间距通过 `gap` CSS 属性处理。
- 网格布局:项目使用网格布局显示。默认为 `auto-fill` 方式生成列,但也可以设置为固定列数。子元素之间的间距通过 `gap` CSS 属性处理。
关于控制区块之间的间距以及启用区块间距控制,请参阅:[什么是 blockGap我该如何使用它](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/#what-is-blockgap-and-how-can-i-use-it)
### 从主题中定位布局或容器区块
布局区块支持旨在通过区块和站点编辑器实现对布局功能的控制。在可能的情况下,尽量使用区块的功能来确定特定的布局需求,而不是依赖额外的样式表。
对于希望定位容器区块以添加或调整特定样式的主题,区块的类名通常是最佳选择。例如 `wp-block-group``wp-block-columns` 这样的类名通常是定位特定区块的可靠类名。除了区块和布局类名外,还有一个由区块和布局组合而成的类名:例如,对于具有受限布局的群组区块,类名将是 `wp-block-group-is-layout-constrained`
对于使用特定布局类型的区块进行定位,请避免定位 `wp-container-`,因为容器类可能不会始终出现在渲染的标记中。
### 从界面控件到HTML标记
若您遵循[区块教程](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/),可深入了解[区块API](https://developer.wordpress.org/block-editor/reference-guides/block-api/)的各个组成部分,并构建专属区块。本文将介绍区块如何让用户编辑其状态的核心概念。
要构建上述交互体验,区块开发者需具备以下要素:
1. **界面控件**:向用户提供选项交互,例如调整区块字体大小。该控件负责读取区块数据(当前是否已设定字体大小?)及其他必要信息(本区块允许使用哪些字号?)。可查阅现有[组件库](https://developer.wordpress.org/block-editor/reference-guides/components/)。
2. **区块属性**:区块需存储数据以记录修改状态,例如是否已设定字号。了解区块如何定义[属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/)。
3. **样式数据接入**控件可能需要获取区块可用样式的外部信息如色彩列表或字号列表。这类预设样式通常由主题定义WordPress也提供默认配置。查看主题可向编辑器提供的[数据列表](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/#settings),以及开发者如何通过[useSetting](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#usesetting)获取这些数据。
4. **将用户样式序列化为HTML标记**用户操作后需相应更新区块HTML标记添加对应类或行内样式。此序列化过程由[编辑/保存](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/)函数与[render_callback](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/creating-dynamic-blocks/)函数实现它们将区块数据转换为HTML代码。
本质上这些是区块开发者实现用户样式定制需关注的核心机制。虽然完全手动可实现但针对通用样式需求可通过自动化API——区块支持功能来简化流程。
### 区块支持API
[区块支持](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)API允许区块声明其支持的功能。通过在[block.json文件](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/)中添加配置信息,区块可向系统声明允许用户执行的操作类型。
例如:
```json
{
"name": "core/paragraph",
"...": "...",
"supports": {
"typography": {
"fontSize": true
}
}
}
```
段落区块在其`block.json`中声明支持字号调整。这意味着区块将显示字号调节控件(除非被主题禁用,详见[主题配置参考](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/)。系统会自动配置控件数据当前字号、可用字号列表并在用户操作时将区块数据序列化为HTML标记自动附加类与行内样式
通过`block.json`使用区块支持机制,开发者仅需数行代码即可实现完整交互体验。查看[区块支持API文档](/docs/reference-guides/block-api/block-supports.md)了解如何为静态或动态区块添加支持功能。
除简化开发流程外,该机制还有以下优势:
- 区块样式信息可被原生移动应用和服务端调用
- 区块对同类样式使用统一控件,确保用户体验一致性
- 所用界面控件将随系统升级自动优化,无需开发者干预
# 编辑器中的样式
本文档将介绍影响区块编辑器用户内容的相关样式核心概念,同时提供对应的参考指南和教程链接,方便读者深入探究每个主题。本文主要面向区块创作者和参与区块编辑器项目的开发人员。
## HTML 与 CSS
用户在区块编辑器中创建内容时,实际上在生成一系列产物:一个 HTML 文档和若干 CSS 样式表(可内嵌于文档或作为外部文件)。
最终生成的 HTML 文档由以下要素共同构成:
- 主题提供的 [WordPress 模板](https://developer.wordpress.org/themes/basics/template-files/)(通过 PHP 实现的经典主题或通过 HTML 模板实现的区块主题)([详细了解](https://developer.wordpress.org/themes/block-themes/#differences-and-similarities-between-classic-themes-and-block-themes)二者差异)
- 具有预定义结构HTML 标记)的[区块](https://developer.wordpress.org/block-editor/reference-guides/core-blocks/)与版式
- 用户对内容的修改:添加内容、转换现有内容(如将段落转换为标题)或调整内容(为区块添加类或内联样式)
前端加载的样式表包含:
- **区块样式**:区块自带的样式表。在前端,您可能会看到 WordPress 定义的所有区块样式合并为单一样式表(`wp-block-library-*`),也可能是每个使用中的区块拥有独立样式表(如 `wp-block-group-*``wp-block-columns-*` 等)。完整说明请参阅[此说明文档](https://make.wordpress.org/core/2021/07/01/block-styles-loading-enhancements-in-wordpress-5-8/)。
- **全局样式**:这些样式通过 theme.json 文件的数据动态生成(参见[说明文档](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/)、[参考指南](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/)和[操作指南](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/))。具体而言,它会整合来自 WordPress 的 theme.json、主题自带的 theme.json如有以及用户通过站点编辑器的全局样式侧边栏提供的数据最终生成一个 ID 为 `global-styles-inline-css` 的内嵌样式表。
- **主题样式**:传统上主题会自行注册样式表,其 ID 基于主题名称(如 `twentytwentytwo-style-css`)。如今除了自有样式表外,主题还可声明包含样式的 theme.json 文件,这些样式将成为全局样式生成样式表的一部分。
- **用户样式**:用户在编辑器中的某些操作会生成样式内容,例如双色调、布局或链接颜色等功能。
- **其他样式**WordPress 核心和插件也可注册样式表。
## 区块样式
自 WordPress 5.0 引入区块编辑器以来,一直提供为用户“添加样式”到特定区块的工具。通过这些工具,用户可为区块附加新类或内联样式,从而改变其视觉呈现。
默认情况下,区块具有预设的 HTML 标记。以段落区块为例:
```html
<p></p>
```
在最简形式下,任何针对 `p` 选择器的样式规则都会作用于该区块,无论这些规则来自区块、主题或其他来源。
用户可通过应用不同样式来改变区块状态:文本对齐方式、颜色、字体大小、行高等。这些状态通过 HTML 属性(主要是 `class``style` 属性,也可以是区块作者认为合适的任何其他属性)反映在区块的 HTML 标记中。
经过用户修改后,初始标记可能变为:
```html
<p class="has-color has-green-color has-font-size has-small-font-size my-custom-class"
style="line-height: 1em"></p>
```
这就是我们所说的“用户提供的区块样式”,也称为“本地样式”或“序列化样式”。本质上,每个工具(字体大小、颜色等)最终都会向区块标记添加若干类和/或内联样式。这些类对应的 CSS 样式属于区块、全局或主题样式表的一部分。
修改区块状态的能力,加上区块可嵌套于其他区块的特性(例如段落区块位于编组区块内),创造了海量的潜在状态和样式组合可能。
#### 设置项到CSS规则的转换
`settings` 部分中所有预设值都将转换为遵循此命名结构的CSS自定义属性`--wp--preset--<类别>-<别名>`。选择器遵循上述样式部分描述的相同规则。
例如,以下 theme.json 配置:
```json
{
"settings": {
"color": {
"palette": {
"default": [
{
"slug": "vivid-red",
"value": "#cf2e2e",
"name": "Vivid Red"
}
],
"theme": [
{
"slug": "foreground",
"value": "#000",
"name": "Foreground"
}
]
}
},
"blocks": {
"core/site-title": {
"color": {
"palette": {
"theme": [
{
"slug": "foreground",
"value": "#1a4548",
"name": "Foreground"
}
]
}
}
}
}
}
}
```
将被转换为以下CSS样式规则
```CSS
body {
--wp--preset--color--vivid-red: #cf2e2e;
--wp--preset--color--foreground: #000;
}
.wp-block-site-title {
--wp--preset--color--foreground: #1a4548;
}
```
除了CSS自定义属性外除双色调外的所有预设都会为每个值生成CSS类。上例还会生成以下CSS类
```CSS
/* vivid-red */
.has-vivid-red-color { color: var(--wp--preset--color--vivid-red) !important; }
.has-vivid-red-background-color { background-color: var(--wp--preset--color--vivid-red) !important; }
.has-vivid-red-border-color { border-color: var(--wp--preset--color--vivid-red) !important; }
/* foreground */
.has-foreground-color { color: var(--wp--preset--color--foreground) !important; }
.has-foreground-background-color { background-color: var(--wp--preset--color--foreground) !important; }
.has-foreground-border-color { border-color: var(--wp--preset--color--foreground) !important; }
/* 站点标题内的foreground */
.wp-block-site-title .has-foreground-color { color: var(--wp--preset--color--foreground) !important; }
.wp-block-site-title .has-foreground-background-color { background-color: var(--wp--preset--color--foreground) !important; }
.wp-block-site-title .has-foreground-border-color { border-color: var(--wp--preset--color--foreground) !important; }
```
### 全局样式API的当前限制
#### 1. **为区块设置不同CSS选择器需服务端注册**
默认情况下,分配给区块的选择器是 `.wp-block-<区块名称>`。但区块可以根据需要更改此设置,可以通过其 `block.json` 中的 `__experimentalSelector` 属性提供CSS选择器。
如果区块这样做,需要在服务端使用 `block.json` 进行注册否则全局样式代码无法访问该信息将使用区块的默认CSS选择器。
#### 2. **无法为不同样式定位不同HTML节点**
每个样式块只能使用单个选择器。
当区块使用 `__experimentalSkipSerialization` 将不同样式属性序列化到包装器以外的不同节点时,这一点尤为重要。详见"区块支持的当前限制"。
#### 3. **每个区块仅支持单一属性**
与区块支持类似,区块只能使用任何样式的单个实例。例如,区块只能拥有单一字体大小。详见相关"区块支持的当前限制"。
#### 4. **仅使用区块支持的区块会显示在全局样式UI中**
站点编辑器中的全局样式UI设有按区块样式的界面。区块列表是使用来自区块 `block.json` 的区块支持动态生成的。如果区块希望被列出,需要使用区块支持机制。
## 布局样式
除了单个区块级别和全局样式中的样式外,还存在为基于区块的主题和经典主题输出的布局样式概念。
布局区块支持输出用于创建布局的区块之间共享的通用布局样式。布局样式对于为作为其他区块容器的任何区块提供通用样式非常有用。依赖这些布局样式的区块示例包括群组、行、列、按钮和社交图标。核心区块通过其 `block.json` 文件中 `supports` 下的 `layout` 设置启用此功能。
布局样式主要输出在两个位置:
### 区块支持API的当前限制
尽管区块支持API具有实用价值但区块开发者仍需注意其存在的一些限制。为了更好地理解这些限制我们以表格区块为例进行说明
```html
<table>
<thead>
<tr>
<th>标题</th>
</tr>
</thead>
<tbody>
<tr>
<th>第一行</th>
</tr>
<tr>
<th>第二行</th>
</tr>
</tbody>
<tfoot>
<tr>
<th>页脚</th>
</tr>
</tfoot>
</table>
```
1. **每个区块仅支持单一样式类型**
在[所有可用样式](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)中区块只能使用每种样式的一个实例。以表格区块为例它只能设置单一字体大小。如果区块开发者希望为表头、主体和页脚设置三种不同字体大小当前区块支持API无法实现。更多详细信息及解决方案请参阅[此议题](https://github.com/WordPress/gutenberg/issues/33255)。
2. **样式仅序列化至区块最外层HTML节点包装器**
区块支持API仅将字体大小值序列化至包装器生成的HTML为`<table class="has-small-font-size">`。当前API无法将该值序列化至其他节点例如`<tbody>`)。
这项工作正处于积极开发阶段,您可以通过[跟踪议题](https://github.com/WordPress/gutenberg/issues/38167)了解进展。相关提案正在探索不同的用户更改序列化方案:不再由每个区块支持单独序列化数据(例如生成`has-small-font-size``has-green-color`等类),而是让区块获得单个类名(例如`wp-style-UUID`并由WordPress在服务器端生成该类的CSS样式。
在该提案持续推进的同时,区块开发者可以使用实验性选项作为解决方案。任何区块支持都可以通过`__experimentalSkipSerialization`跳过HTML标记的序列化。例如
```json
{
"name": "core/paragraph",
"...": "...",
"supports": {
"typography": {
"fontSize": true,
"__experimentalSkipSerialization": true
}
}
}
```
这意味着排版区块支持将执行所有操作创建UI控件、将区块属性绑定到控件等但不会将用户值序列化到HTML标记中。类和内联样式不会自动应用于包装器区块开发者需要在`edit``save``render_callback`函数中实现此功能。具体实现示例请参阅[此议题](https://github.com/WordPress/gutenberg/issues/28913)。
请注意,如果为组(排版、颜色、间距)启用`__experimentalSkipSerialization`将影响该组内_所有_区块支持。在上例中`typography`组内的_所有_属性`fontSize``lineHeight``fontFamily`等)都会受到影响。
若仅针对_单个_属性启用可以使用数组声明要跳过的属性。下例中仅`fontSize`会跳过序列化,`typography`组内的其他项目(如`lineHeight``fontFamily`等)不受影响:
```json
{
"name": "core/paragraph",
"...": "...",
"supports": {
"typography": {
"fontSize": true,
"lineHeight": true,
"__experimentalSkipSerialization": [ "fontSize" ]
}
}
}
```
该功能支持已[通过此PR添加](https://github.com/WordPress/gutenberg/pull/36293)。
## 全局样式
全局样式是指生成全站样式的一种机制。与前一节所述的区块样式不同这些样式不会序列化到文章内容中也不会附加到区块HTML。该系统的输出是一个ID为`global-styles-inline-css`的新样式表。
该机制[于WordPress 5.8引入](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/)。当时仅从WordPress和启用主题获取数据。WordPress 5.9将该系统扩展为同时接收来自用户的样式数据。
基本数据流如下:
![全局样式数据流](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/explanations/architecture/assets/global-styles-input-output.png)
样式表的生成过程主要包含三个步骤:
1. 收集数据:[WordPress内置](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/theme.json)的`theme.json`文件、启用主题的`theme.json`文件如果存在以及用户通过站点编辑器中全局样式UI提供的样式
2. 整合数据对来自不同来源WordPress默认值、主题和用户的结构化信息进行标准化处理并合并为统一结构
3. 将数据转换为样式表将内部表示转换为CSS样式规则并将其加入样式表队列
### 收集数据
数据可能来自三个不同来源WordPress 默认配置、当前启用主题或用户设置。这三类数据均采用统一的 [`theme.json` 格式](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/)。
WordPress 和当前主题的数据从对应的 `theme.json` 文件中获取。用户数据则从数据库中提取,这些数据是用户通过站点编辑器的全局样式侧边栏保存修改后存储的。
### 整合数据
此阶段的目标是构建统一的数据结构。
该阶段包含两个重要处理流程:首先,系统需要对所有输入数据进行标准化处理,因为不同来源可能使用不同版本的 `theme.json`格式(例如某个主题可能使用 [v1 版本](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-v1/),而 WordPress 基础框架使用[最新版本](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/))。其次,系统需要确定如何将输入数据合并为统一结构,这将是后续章节的重点内容。
#### 样式处理
对于 `theme.json` 结构中的不同部分,系统会采用差异化处理方式。`styles` 章节中的数据遵循以下逻辑进行融合:用户数据会覆盖主题数据,主题数据会覆盖 WordPress 数据。
例如,假设我们分别从 WordPress、主题和用户获取到以下三个 `theme.json` 结构:
```json
{
"styles": {
"color": {
"background": "<WordPress 值>"
},
"typography": {
"fontSize": "<WordPress 值>"
}
}
}
```
```json
{
"styles": {
"typography": {
"fontSize": "<主题值>",
"lineHeight": "<主题值>"
}
}
}
```
```json
{
"styles": {
"typography": {
"lineHeight": "<用户值>"
}
}
}
```
整合后的结果将是:
```json
{
"styles": {
"color": {
"background": "<WordPress 值>"
},
"typography": {
"fontSize": "<主题值>",
"lineHeight": "<用户值>"
}
}
}
```
#### 设置处理
`settings` 章节的处理方式与样式不同。大部分设置仅用于编辑器配置,不会影响全局样式。其中仅预设值会最终影响样式表输出。
预设值是指在界面不同区域向用户展示的预定义样式,例如调色板或字体尺寸。包含以下设置项:`color.duotone``color.gradients``color.palette``typography.fontFamilies``typography.fontSizes`。与 `styles` 不同,来自不同来源的预设值不会相互覆盖,而是全部保存在整合后的结构中。
例如,假设我们分别从 WordPress、主题和用户获取到以下三个 `theme.json` 结构:
```json
{
"settings": {
"color": {
"palette": [ "<WordPress 值>" ],
"gradients": [ "<WordPress 值>" ]
}
}
}
```
```json
{
"settings": {
"color": {
"palette": [ "<主题值>" ]
},
"typography": {
"fontFamilies": [ "<主题值>" ]
}
}
}
```
```json
{
"settings": {
"color": {
"palette": [ "<用户值>" ]
}
}
}
```
整合后的结果将是:
```json
{
"settings": {
"color": {
"palette": {
"default": [ "<WordPress 值>" ],
"theme": [ "<主题值>" ],
"user": [ "<用户值>" ]
},
"gradients": {
"default": [ "<WordPress 值>" ]
}
},
"typography": {
"fontFamilies": {
"theme": [ "<主题值>" ]
}
}
}
}
```
### 从数据到样式
生成样式表的最后阶段是将整合数据转换为 CSS 样式规则。
#### 样式转 CSS 规则
可以将 `styles` 章节视为 CSS 规则的结构化表示,每个数据块对应一条 CSS 规则:
- theme.json 中的键值对映射为 CSS 声明(`property: value`
- 每个数据块的 CSS 选择器根据其语义生成:
- 顶层章节使用 `body` 选择器
- 顶层元素使用与其表示的 HTML 元素匹配的 ID 选择器(例如 `h1``a`
- 区块使用默认生成的类名(`core/group` 转换为 `.wp-block-group`),除非通过 `block.json` 明确设置不同选择器(`core/paragraph` 转换为 `p`)。详见“当前限制”章节说明
- 区块内的元素使用区块选择器与元素选择器的组合
例如,以下 `theme.json` 结构:
```json
{
"styles": {
"typography": {
"fontSize": "<顶层值>"
},
"elements": {
"h1": {
"typography": {
"fontSize": "<h1 值>"
}
}
},
"blocks": {
"core/paragraph": {
"color": {
"text": "<段落值>"
}
},
"core/group": {
"color": {
"text": "<群组值>"
},
"elements": {
"h1": {
"color": {
"text": "<群组内 h1 值>"
}
}
}
}
}
}
}
```
将被转换为以下 CSS
```css
body {
font-size: <>;
}
h1 {
font-size: <h1 >;
}
p {
color: <>;
}
.wp-block-group {
color: <>;
}
.wp-block-group h1 {
color: < h1 >;
}
```