Xây một block
Ship một block do app bạn sở hữu, để chủ cửa hàng kéo được nó vào trang trong trình sửa, và sau đó nó vẫn tiếp tục render từ app của bạn.
Block là thứ app của bạn đóng góp vào trình sửa trang của chủ cửa hàng: một mảnh trang dựng sẵn, kèm theo một nhóm nhỏ các nút điều khiển mà chủ cửa hàng được phép đổi trên đó. Họ kéo nó ra từ bảng phần tử như mọi phần tử khác; nó render trên storefront của họ; và khi bạn ship một bản sửa lỗi, mọi cửa hàng đã cài app bạn đều nhận được.
Trang này giả định bạn đã có sẵn một app. Nếu chưa, bắt đầu từ đó — một block thuộc về một phiên bản của app, và phiên bản mới là thứ được duyệt.
1. Block là gì, và không phải là gì
Một block là một cây con gồm các phần tử của chính nền tảng này, cộng với một thanh trait được khai báo.
Nó không phải là markup. Bạn không viết HTML, CSS hay template. Bạn mô tả một
cây các node — flex-block, heading, button, image — với đúng những thuộc
tính mà phần tử của chính chủ cửa hàng có, rồi nền tảng này render nó.
Ràng buộc đó không phải sở thích về phong cách, và nó đáng được một đoạn nói thẳng, vì nó sẽ định hình mọi thứ bạn dựng ở đây. Nền tảng này render mỗi trang hai lần: một lần trong trình duyệt, trên canvas của trình sửa, và một lần bằng Go, khi trang được xuất bản. Hai bộ render đó phải cho ra HTML giống nhau đến từng byte — đó là bất biến mà cả sản phẩm này đứng trên, và nó được canh bởi một bài test render một tài liệu qua cả hai rồi so sánh. Markup mà chỉ một trong hai biết vẽ sẽ phá vỡ điều đó. Nên một block được diễn đạt bằng vốn từ mà cả hai bộ render đã cùng có, và cái giá bạn trả là bạn không thể tự nghĩ ra một phần tử mới. Thứ bạn nhận lại là block của bạn hành xử y hệt một phần tử gốc: nó đáp ứng các breakpoint của chủ cửa hàng, theme của họ, font của họ, và nó vẫn chạy đúng khi bất kỳ thứ nào trong đó thay đổi.
Nếu block của bạn cần làm một việc gì đó — đếm ngược, carousel, gọi fetch — thì đó là một island, và nó ở mục 6.
2. Manifest
Một lệnh PUT thay thế toàn bộ phần đóng góp vào bảng phần tử của phiên bản của
bạn. Cả tập hợp mới là thứ người duyệt phê duyệt, nên không có route riêng cho
từng block: gửi hết, mọi lần.
PUT /api/orgs/{orgId}/apps/{appId}/versions/{versionId}/blocks
Authorization: Bearer <user token của bạn>
Content-Type: application/json
Một manifest đầy đủ và hợp lệ:
{
"blocks": [
{
"key": "loyalty-badge",
"name": "Loyalty badge",
"icon": "award",
"rootId": "wrap",
"nodes": [
{
"id": "wrap",
"type": "flex-block",
"children": ["title"],
"props": { "style": { "gap": "8px" }, "config": {}, "specials": {} }
},
{
"id": "title",
"type": "heading",
"props": { "style": {}, "config": {}, "specials": { "text": "Members save 10%" } }
}
],
"slots": [{ "name": "title", "nodeId": "title" }],
"traits": [
{
"key": "look",
"label": "Appearance",
"attributes": [
{ "widget": "text_color", "slot": "title", "target": "style", "writeKey": "color" }
]
}
]
}
]
}
Từng trường một:
| trường | nó là gì |
|---|---|
key |
id của bạn cho dòng này, dạng slug viết thường. Nó là một nửa của tham chiếu mà trang của chủ cửa hàng lưu lại, nên đã ship rồi thì không được đổi. |
name |
thứ chủ cửa hàng đọc thấy trên bảng phần tử. |
icon |
biểu tượng trên bảng phần tử. Tên không tra được sẽ lùi về mặc định — riêng trường này không bị đối chiếu với một vốn từ nào. |
rootId |
node nào được thả xuống. Nêu rõ ràng, không bao giờ suy đoán. |
nodes[] |
cây con. id là của bạn và chỉ có nghĩa trong phạm vi block; các id trên trang của chủ cửa hàng được cấp lúc thả. |
nodes[].type |
một loại phần tử mà nền tảng này render được. Có kiểm tra. |
nodes[].props |
phần style / config / specials của chính node, đưa qua nguyên văn. |
slots[] |
đặt tên cho một node bên trong để một trait có thể trỏ tới. |
traits[] |
các nút điều khiển của chủ cửa hàng — mục 3. |
Giới hạn: 24 block mỗi phiên bản, 1 MiB cho cả tài liệu.
Vốn từ đến từ đâu
type và widget được đối chiếu với những gì trình sửa thật sự đang ship, chứ
không phải với một danh sách trong tài liệu này — phép kiểm tra đọc một vốn từ
được sinh tự động và di chuyển cùng trình sửa. Có hai hệ quả đáng tính trước:
- một cái tên hôm nay còn đúng có thể bị đổi tên hoặc xoá bởi một bản phát hành sau của trình sửa. Bản nháp bạn đã lưu sẽ được kiểm tra lại lúc gửi duyệt, đúng vì lý do này.
- trong hướng dẫn này không có danh sách ổn định các loại phần tử, bởi một danh sách đặt ở đây sẽ sai trong vòng một bản phát hành. Thông báo lỗi sẽ nói bạn sai ở đâu, kèm tên.
3. Thanh trait
Thanh trait là toàn bộ bề mặt chỉnh sửa của chủ cửa hàng trên block của bạn. Bạn khai báo gì thì họ đổi được thứ đó; mọi thứ còn lại là của bạn.
Một attribute có bốn phần:
{ "widget": "text_color", "slot": "title", "target": "style", "writeKey": "color" }
widget— trình sửa render nút điều khiển nào. Đối chiếu với vốn từ widget của trình sửa.slot— nó sửa node nào, theo cái tên bạn đặt trongslots[]. Slot dành riêng$roottrỏ tới chính node gốc của block.target— một trongstyle,config,specials.writeKey— khoá mà widget đó ghi vào.
Một khai báo mở ra cả một VÙNG TÊN, không phải một khoá
Đây là chỗ hay làm người ta bất ngờ, nên xin nói thẳng: khai báo
{ target: "style", writeKey: "color" } trên một slot sẽ mở toàn bộ vùng tên
style của node đó cho chủ cửa hàng, chứ không phải riêng khoá color.
Đó là chủ ý. Phần lớn widget ghi nhiều hơn một khoá — một nút chỉnh viền ghi bốn
cạnh, một nút chỉnh đổ bóng ghi độ lệch, độ nhoè và màu — và phân quyền theo
từng khoá sẽ làm những widget đó chạy nửa vời theo kiểu trông giống lỗi hơn là
giống quy tắc. Nên quyền là theo vùng tên, còn writeKey vẫn bắt buộc vì nó ghi
lại nút điều khiển đó dùng để làm gì và nó bị đối chiếu với vốn từ khoá-ghi của
nền tảng.
Điều này có nghĩa gì với bạn: nếu bạn thấy không thoải mái khi chủ cửa hàng đổi
bất cứ thứ gì trong style của một node, thì đừng khai báo attribute style
trên node đó. Hãy tách thiết kế thành nhiều node hơn rồi khai báo đúng cái bạn
định cho.
Ba vùng tên
| target | chứa |
|---|---|
style |
CSS theo từng breakpoint — màu, kích thước, khoảng cách |
config |
dữ liệu theo từng breakpoint nhưng không phải CSS |
specials |
nội dung và định danh chỉ ở mức gốc — chữ, thẻ được chọn |
4. Chủ cửa hàng đổi được gì và không đổi được gì
Một block đã thả xuống là niêm phong. Chủ cửa hàng không chọn, không sửa, không di chuyển, không nhân bản và không xoá được bất cứ thứ gì bên trong nó, và họ cũng không dán được gì vào trong. Thanh trait bạn khai báo là toàn bộ bề mặt.
Thứ họ làm được là xoá chính block đó, và đổi bất cứ gì thanh trait của bạn mở ra.
Thiết lập của họ sống sót qua các bản cập nhật của bạn. Thay đổi của chủ cửa hàng được lưu trên tham chiếu — đúng một node duy nhất mà trang của họ giữ — đánh khoá theo slot, và được áp lại mỗi lần block được dựng ra. Nên khi bạn ship một phiên bản mới, màu họ chọn vẫn là màu họ chọn.
Hai hệ quả của thiết kế đó mà bạn nên biết:
- một thiết lập cho nút điều khiển mà bạn gỡ bỏ ở phiên bản sau sẽ nằm im. Nó không bị xoá và cũng không được áp lại; nếu bạn thêm nút đó trở lại, thiết lập quay lại cùng nó.
- trang của họ lưu một tham chiếu tới block của bạn, không phải một bản sao. Không có bản rẽ nhánh nào của markup của bạn nằm ở đâu cả. Đó là điều làm cho câu "ship một bản sửa và mọi site khách hàng đều được sửa" là thật — và đó cũng là lý do manifest phải qua duyệt.
5. Phiên bản
Việc duyệt gắn vào một phiên bản, không bao giờ gắn vào app của bạn, bởi duyệt là ghim chặt một payload: nếu nó gắn vào app thì bạn có thể đổi chính thứ mà người duyệt đã phê duyệt.
Khi một phiên bản đã được duyệt:
- một phiên bản đã duyệt với cùng bộ scope sẽ tự động chuyển mọi bản cài sang. Chủ cửa hàng không phải làm gì, không phải đồng ý lại — vì scope không đổi. Đây chính là mục đích của cả thiết kế: bạn sửa một chỗ, và mọi cửa hàng đã cài app bạn đều được sửa.
- một phiên bản đòi NHIỀU hơn thứ chủ cửa hàng đã cấp sẽ dừng lại và hỏi. Nó chờ họ đồng ý, trên màn hình đồng ý của họ.
- chủ cửa hàng có thể quay về bất kỳ phiên bản đã duyệt nào trước đó, và được cho biết trước những block nào của bạn mà phiên bản đó không đóng góp — nên một lần quay lui làm mất hẳn một mảng trên trang đang chạy của họ sẽ nói ra trước.
Nếu bạn ship một phiên bản rồi phát hiện nó hỏng, hãy rút nó về:
POST /api/orgs/{orgId}/apps/{appId}/versions/{versionId}/withdraw
DELETE /api/orgs/{orgId}/apps/{appId}/versions/{versionId}/withdraw (khôi phục)
Việc rút về cố ý bất đối xứng: không ai mới đáp được xuống phiên bản đó nữa, và mọi cửa hàng đang chạy nó thì vẫn chạy tiếp. Giật một block đang sống ra khỏi trang của chủ cửa hàng là một phương thuốc tệ hơn cả căn bệnh. Muốn chuyển họ khỏi nó, hãy ship một phiên bản đã sửa — họ sẽ được chuyển sang tự động nếu scope khớp.
Khi nào bản cập nhật của bạn tới được storefront
Ngay lập tức trong trình sửa của chủ cửa hàng. Trên trang đã xuất bản của họ thì vào lần xuất bản kế tiếp của trang đó — nền tảng render lại mọi trang đang chạy còn phục vụ một bản dựng cũ của block bạn, vào lần tiếp theo chủ cửa hàng xuất bản bất cứ thứ gì. Nó không tự ý xuất bản lại site của họ thay bạn.
6. Island: làm cho một block biết làm việc
Island là một module JavaScript do app bạn ship. Một node trong manifest của bạn nêu tên nó, và trên trang đã xuất bản, node đó được hydrate bằng mã của bạn.
Ba bước.
Khai báo cái tên. Chỉ tên thôi — mã đến sau, bằng một đường khác.
PUT /api/orgs/{orgId}/apps/{appId}/versions/{versionId}/islands
{ "islands": [{ "name": "countdown" }] }
Trỏ một node vào nó, bằng trường island trên node đó trong manifest block
của bạn:
{ "id": "wrap", "type": "flex-block", "island": "countdown", "children": ["title"] }
Tải module lên. Body chính là đoạn JavaScript — không có phong bì multipart, vì đây là một bước build chứ không phải một hộp thoại chọn tệp.
POST /api/orgs/{orgId}/apps/{appId}/versions/{versionId}/islands/module?name=countdown
Content-Type: text/javascript
export default class { … }
Module của bạn tự đăng ký dưới cái tên nó được cấp:
window.WB.register('app.app_1a2b3c4d.countdown', MyIsland);
Bạn sẽ gửi request đó ở mỗi lần sửa.
sb devtheo dõi tệp và gửi lại mỗi lần bạn lưu — vẫn đúng lệnhPOSTđó, và sau đó vẫn phải tải lại trang, bởi mã của một island được phục vụ từ object store của nền tảng chứ không phải từ máy bạn.sb deploygửi manifest ở trên cùng với mọi module, khai báo đi trước.
Nền tảng đặt vùng tên cho island của bạn, và tự lưu trữ nó
Có hai điều đi kèm, và cả hai sẽ cắn bạn nếu bạn bỏ qua.
Cái tên bạn khai báo được gắn vùng tên. Bạn khai báo countdown; trang mang
app.<appId của bạn>.countdown. Cả trang chỉ có một bảng đăng ký, nên hai app
cùng ship một Countdown sẽ âm thầm thay thế nhau — block của agency này bắt đầu
chạy mã của agency kia. Việc gắn vùng tên khiến điều đó thành bất khả thi, thay
vì chỉ là khó xảy ra. Hệ quả:
- tên bạn khai báo không được chứa dấu
.— đó là ký tự phân tách, và cho phép nó sẽ mở đường cho một manifest mạo danh nhà phát hành khác. Nó cũng không được chứa dấu cách hay#, là các ký tự phân tách danh sách và phương thức mà trang dùng. - hãy đăng ký cái tên đã gắn vùng tên, không phải cái tên trần. App id nằm trên app của bạn; dấu hiệu trên phần tử đã xuất bản mang đúng chuỗi đó.
Chúng tôi lưu trữ module của bạn, bạn không trỏ link tới nó. Phần byte được tải lên cùng phiên bản và do nền tảng này phục vụ. Khoá lưu trữ là SHA-256 của phần byte đó, và điều này có một hệ quả bạn sẽ nhận ra: tải lên hai lần cùng một nội dung sẽ cho cùng một khoá và không lưu thêm gì mới, còn hai phiên bản ship cùng một module thì dùng chung một object. Nó cũng có nghĩa module của một phiên bản đã duyệt thì không bao giờ đổi được — một lần tải lên sau này vào một bản nháp không chạm tới được nó.
Chúng tôi lưu trữ nó thay vì đi lấy từ một URL do bạn cung cấp, bởi URL chỉ là một con trỏ: người duyệt sẽ đang phê duyệt một địa chỉ mà nội dung của nó bạn có thể đổi ngay hôm sau. Thứ việc duyệt mua được là một hiện vật ổn định và một nhà phát hành không thể lặng lẽ đổi thứ đã ship.
Giới hạn: 12 island mỗi phiên bản, 512 KiB mỗi module.
Chúng tôi kiểm được gì và không kiểm được gì
Chúng tôi kiểm kích thước, và kiểm rằng phần byte là văn bản. Chúng tôi không kiểm được module của bạn có phải JavaScript hay không, bởi JavaScript không có phần đầu tệp nào để nhận diện — bất cứ đoạn văn bản nào cũng có thể là một module. Thứ kiểm soát được việc mã của bạn làm gì là khâu duyệt, và đó là thứ duy nhất.
Cũng xin nói rõ điều đó nghĩa gì với chính bạn: việc duyệt mang lại cho người dùng của bạn một hiện vật ổn định, kiểm toán được, và một danh tính nhà phát hành. Nó không, và không thể, chứng minh hành vi — một island có thể gọi API của chính bạn lúc chạy, đó là chuyện bình thường và được lường trước. Chủ cửa hàng được cho biết, ngay trên thẻ ứng dụng ở chợ trước khi họ cài, rằng app của bạn chạy mã trên cửa hàng của họ.
7. Dữ liệu do app sở hữu: đưa nội dung của bạn vào HTML
Một island chạy sau khi tài liệu đã về, nên không thứ gì nó vẽ ra nằm trong HTML mà một cỗ máy tìm kiếm đọc được. Nếu thứ app bạn bán chính là nội dung — đánh giá, xếp hạng, thông số, huy hiệu — thì một mình island để nó vô hình với tìm kiếm.
Dữ liệu do app sở hữu khoả lấp chỗ đó. Bạn lưu giá trị trên cửa hàng của chủ cửa hàng; trang của họ RÀNG BUỘC vào chúng; server in chúng thẳng vào HTML đã xuất bản. Không island nào tham gia, và không markup nào từ bạn.
Lưu một giá trị
PUT /api/v1/app-data
Authorization: Bearer <token wba_ của app bạn>
{ "key": "rating", "value": "4.6 out of 5" }
Trong body đó không có app id, và cũng không có trường nào cho nó. Vùng tên đến từ token của bạn, nên bạn không ghi được vào vùng tên của app khác — cùng một quy tắc với tên island của bạn, chỉ khác cánh cửa.
PUT mà không có id trên đường dẫn: một giá trị được gọi tên bằng chính khoá của
nó, nên đặt hai lần cũng là đặt một lần. Bạn không bao giờ phải đọc trước khi
ghi, và một lần thử lại sau timeout không thể tạo ra bản trùng.
Bạn cần scope appdata.write lúc cài, và appdata.read để đọc lại chính
các giá trị của mình bằng GET /api/v1/app-data. Xoá một giá trị bằng
DELETE /api/v1/app-data/{key}.
Hai kiểu chủ sở hữu, và cách trỏ tới một trong hai
Một giá trị hoặc nói về CỬA HÀNG hoặc nói về một SẢN PHẨM, và bạn chọn cái nào sẽ quyết định nó đọc được ở đâu.
| chủ sở hữu | bạn gửi | giá trị nói về |
|---|---|---|
site (mặc định) |
không gửi gì, hoặc "owner":"site" |
cả cửa hàng |
product |
"owner":"product","ownerId":"<id sản phẩm>" |
đúng sản phẩm đó |
PUT /api/v1/app-data
{ "owner": "product", "ownerId": "prd_1a2b", "key": "rating", "value": "4.9" }
Hãy chọn product cho bất cứ thứ gì thay đổi theo từng sản phẩm, mà đó là
phần lớn những gì khiến việc này đáng làm. Xếp hạng là ví dụ điển hình: một
rating ở mức site sẽ in cùng một con số lên mọi trang sản phẩm, tệ hơn là
không in gì cả. Một giá trị product mà thiếu ownerId sẽ bị từ chối, thay vì
âm thầm lưu thành giá trị mức site ở nơi không bao giờ có ai tìm thấy.
Giới hạn: 500 giá trị cho mỗi app trên mỗi cửa hàng, 8 KiB mỗi giá trị. Một giá trị rốt cuộc nằm bên trong HTML của một trang mà người mua tải về, nên sức nặng của nó là sức nặng trang của chủ cửa hàng.
Đọc nó từ một block
Khai báo một binding trên node của block bạn, với nguồn app.<key>:
{
"id": "rating-text",
"type": "heading",
"props": {
"specials": { "text": "No rating yet" },
"bindings": [{ "source": "app.rating", "field": "specials.text" }]
}
}
Bạn viết app.rating, không bao giờ viết app.<appId của bạn>.rating. Nền
tảng tự điền app id vào lúc nó dựng block của bạn ra, và chính điều đó khiến vùng
tên của app khác trở nên không với tới được — bạn không gọi tên nó được, nên
chẳng có gì phải canh và chẳng có gì bạn làm sai được.
Một giá trị thuộc site phân giải được ở bất cứ đâu trên trang. Một giá trị
thuộc product phân giải trên một node đã ràng buộc vào sản phẩm đó, tức là thứ
mà target của binding gọi tên.
Chủ cửa hàng thấy gì khi app của bạn ra đi
Gỡ cài đặt app của bạn sẽ ngừng phân giải dữ liệu của bạn ngay lập tức và không xoá gì cả. Mọi phần tử đang ràng buộc vào một giá trị của bạn sẽ lùi về đúng đoạn chữ mà nó được soạn ban đầu — chứ không lùi về một khoảng trống — nên một trang không bao giờ vỡ chỉ vì một app rời đi. Cài lại là mọi thứ trở về, không ai phải làm gì.
Dữ liệu của bạn được giữ vô thời hạn, và chủ cửa hàng có thể chủ động xoá sạch nếu họ muốn nó biến mất. Đó là quyết định của họ, không phải của một cái đồng hồ đếm.
Điều tương tự áp dụng khi app của bạn chỉ đang bị tắt: một bản cài đã tắt không phân giải gì cả, và bật lại là mọi thứ trở về.
Giới hạn mà điều này KHÔNG bao gồm
Chủ cửa hàng không ràng buộc dữ liệu của bạn lên phần tử của chính họ được.
Cách này chạy cho một binding do một BLOCK khai báo — một binding bạn viết,
trong manifest của bạn. Một chủ cửa hàng muốn trỏ heading của chính họ vào
app.<bạn>.rating sẽ phải gọi đích danh tên app của bạn, việc đó cần một bộ chọn
trong trình sửa và một phép kiểm tra lúc đọc, cả hai đều chưa tồn tại. Đừng dựng
một sản phẩm dựa trên việc chủ cửa hàng tự tay nối dây các giá trị của bạn; hãy
đặt binding vào trong block của bạn.
8. Khi có thứ bị thiếu
Block của bạn trước hết là HTML render từ server. Một island chỉ thêm hành vi vào markup vốn đã render xong, nên:
- người mua tắt JavaScript, hoặc module không tải được — block vẫn render đúng như lẽ ra nó phải render, chỉ thiếu phần hành vi. Không có gì sụp xuống, không có ô trống. Một dòng trong console của trình duyệt.
- island của bạn đã khai báo nhưng chưa tải module lên — bạn không gửi duyệt được. Một cái tên không có gì phía sau sẽ là một dấu hiệu trỏ tới một module không ai phục vụ, và trang sẽ âm thầm không làm gì.
- chủ cửa hàng gỡ cài đặt app của bạn — mọi block của bạn biến khỏi trang của họ ở lần đọc kế tiếp. Họ được cảnh báo trước, và được cho biết việc đó ảnh hưởng những trang nào và những block nào.
- app của bạn bị đình chỉ, hoặc phiên bản của bạn bị rút về mà họ chưa từng ở trên đó — giống như gỡ cài đặt, xét về mặt block; còn riêng việc rút về thì không ảnh hưởng tới các cửa hàng đã chạy phiên bản đó.
Hãy chủ động thiết kế cho trường hợp đầu tiên: đặt nội dung của bạn vào trong markup và dùng island để tăng cường nó, chứ không phải để lấp đầy một cái vỏ rỗng.
9. Những lời từ chối bạn sẽ thật sự gặp
Mọi lời từ chối đều là JSON, có một code cho máy đọc và một thông báo nói rõ
cái gì sai.
Manifest (PUT …/blocks)
| code | status | nghĩa là |
|---|---|---|
unknown_element_type |
400 | một nodes[].type mà nền tảng này không render |
unknown_trait_widget |
400 | một widget mà trình sửa không có |
unknown_write_key |
400 | một writeKey không hợp lệ với target đó |
invalid_block |
400 | sai quy tắc hình dạng: key hỏng, thiếu name, không có rootId |
invalid_block_subtree |
400 | các node không tạo thành một cây duy nhất từ rootId |
unknown_island |
409 | một node gọi tên một island mà phiên bản không khai báo |
duplicate_block_key |
409 | hai block trùng key |
too_many_blocks |
409 | vượt quá 24 |
not_draft |
409 | phiên bản đã rời trạng thái nháp — hãy tạo một bản mới |
body_too_large |
413 | vượt quá 1 MiB |
block_vocabulary_unavailable |
503 | ngay lúc này server không kiểm tên được; không có gì được lưu |
Island (PUT …/islands, POST …/islands/module)
| code | status | nghĩa là |
|---|---|---|
invalid_island_name |
400 | không phải slug viết thường, hoặc có chứa ., dấu cách hay # |
island_name_required |
400 | lần tải lên không nói là island nào |
island_module_not_text |
400 | rỗng, hoặc không phải UTF-8 hợp lệ |
duplicate_island_name |
409 | hai island trùng tên |
too_many_islands |
409 | vượt quá 12 |
island_not_found |
404 | tải lên cho một cái tên mà phiên bản không khai báo |
island_module_too_large |
413 | vượt quá 512 KiB |
image_storage_unavailable |
503 | bản triển khai này không lưu được tệp tải lên |
Gửi duyệt (POST …/submit) chạy lại mọi phép kiểm tra ở trên trên phần đã
lưu, bởi một widget có thể bị đổi tên giữa ngày bạn lưu và ngày bạn gửi. Nó
có thêm một mã của riêng mình:
| code | status | nghĩa là |
|---|---|---|
island_no_module |
409 | một island đã khai báo mà chưa tải gì lên |
10. Những giới hạn đã biết, nói thẳng thay vì nói tránh
- Không bài test nào trong bộ test của chính nền tảng này chạy một module của app. Mọi lớp bao quanh nó đều được test — manifest, việc tải lên, dấu hiệu, địa chỉ — nhưng không có gì ở đây chạy một dòng nào của một island bên thứ ba. Việc module của bạn hydrate trên một trang đã xuất bản là do bạn kiểm thử, không phải chúng tôi. Hãy coi đó là lý do để thử trên một trang đã xuất bản thật trước khi gửi duyệt.
- Island không chạy trên canvas của trình sửa. Dấu hiệu chỉ được phát ra lúc xuất bản, nên chủ cửa hàng thấy markup của bạn trong trình sửa còn hành vi của bạn thì chỉ thấy trên storefront. Đó là hợp đồng render ở mục 1 đang hoạt động đúng ý đồ, không phải lỗi.
keycủa block không có cơ chế đánh phiên bản. Một key là một nửa của tham chiếu mà trang của chủ cửa hàng lưu, nên đổi tên nó ở phiên bản sau không di trú trang của họ — nó gỡ block khỏi trang họ. Hãy chọn những key mà bạn sống chung được lâu dài.- Một block không chứa được block khác. Một node trong manifest tự nhận là tham chiếu tới một block sẽ bị tước bỏ lời tự nhận đó.
Cập nhật 22/8/2026