Store Builder

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.

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

typewidget đượ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 trong slots[]. Slot dành riêng $root trỏ tới chính node gốc của block.
  • target — một trong style, 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 dev theo dõi tệp và gửi lại mỗi lần bạn lưu — vẫn đúng lệnh POST đó, 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 deploy gử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ứckhô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.
  • key củ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