บล็อก

💬 การเสนอแนวทางใหม่สำหรับ 'Gutenberg และแอปพลิเคชันแบบ Decoupled'

Leonardo Losoviz
โดย Leonardo Losoviz ·

เมื่อไม่กี่วันก่อน Jason Bahl ผู้สร้าง WPGraphQL ได้เผยแพร่ Gutenberg and Decoupled Applications โดยวิเคราะห์ข้อดีและข้อจำกัดของ 3 แนวทางในการรวม GraphQL เข้ากับ Gutenberg

สัปดาห์ก่อนหน้านั้น เขาได้พูดถึงบน Twitter ว่าแนวทางของ Gato GraphQL ในการสร้างโมเดล Gutenberg นั้นไม่เหมาะสม:

ในความเห็นของฉัน นี่ไม่ใช่สิ่งที่ควรอวด สิ่งหนึ่งที่ GraphQL พยายามแก้ไขด้วย Typed Schema คือการมอบความสามารถในการคาดเดาและความสอดคล้องให้กับไคลเอนต์ และให้ไคลเอนต์สามารถกำหนดสิ่งที่ต้องการได้ถึงระดับฟิลด์

การส่งคืนประเภท "Object" แบบ wildcard ที่ไม่มีรูปแบบที่คาดเดาได้ หมายความว่าแอปพลิเคชันไคลเอนต์อาจเสียหายได้ตลอดเวลา เพราะไม่มีสัญญาระหว่างเซิร์ฟเวอร์กับไคลเอนต์อีกต่อไป เซิร์ฟเวอร์ได้ถอดการควบคุมออกจากมือของไคลเอนต์แล้ว

ผ่านบทความนี้ ฉันจะเข้าร่วมการสนทนา โดยจะตอบสนองต่อคำวิจารณ์ของ Jason และในระหว่างนั้นจะอธิบายแนวทางของปลั๊กอินของฉัน พร้อมทั้งแสดงให้เห็นว่าเหตุใดฉันจึงเชื่อว่ามันสามารถเหมาะสมกับ Gutenberg ได้เป็นอย่างดี

การใช้ COPE เพื่อดึงข้อมูล metadata ของ Gutenberg

โซลูชันของฉันอาจถือได้ว่าเป็นแนวทางที่ 4 ซึ่งมีรายละเอียดดังนี้:

ในการรับข้อมูล Gutenberg เพื่อขับเคลื่อน GraphQL ไม่ต้องสร้างสคีมาเพิ่มเติมในฝั่ง PHP หรือทำสำเนาข้อมูลที่มีอยู่ แต่ให้ดึงข้อมูลจากเนื้อหาที่บันทึกไว้ในบล็อก โดยใช้กลยุทธ์ COPE ("Create Once, Publish Everywhere")

(COPE คือกลยุทธ์ที่ช่วยให้มีแหล่งข้อมูลที่เชื่อถือได้เพียงแหล่งเดียว และเปิดเผยข้อมูลนั้นให้กับแอปพลิเคชันต่างๆ ในกรณีของเรา แหล่งข้อมูลที่เชื่อถือได้คือข้อมูลบล็อก Gutenberg ที่บันทึกไว้ในฐานข้อมูล ฉันได้อธิบาย COPE และการนำไปใช้งานกับ WordPress ไว้ในบทความนี้)

ในที่สุด เราสามารถใช้ GraphQL เพื่อดึงข้อมูลที่สกัดออกมาสำหรับบล็อก Gutenberg ใดก็ได้ โดยการแมปบล็อกทั้งหมดไปยังประเภท Block เดียว

กลยุทธ์นี้คือการแลกเปลี่ยน ไม่ใช่การแก้ปัญหาที่ชัดเจน

กลยุทธ์นี้ไม่ได้แก้ปัญหาที่ Jason ชี้ให้เห็น นั่นคือการขาดสคีมาในฝั่งเซิร์ฟเวอร์ ซึ่งจะทำให้สร้างสัญญาระหว่างเซิร์ฟเวอร์และไคลเอนต์ได้

COPE ไม่สามารถแก้ปัญหานี้ได้เพราะเราไม่สามารถสร้างสคีมาขึ้นมาใหม่ได้จากเนื้อหาที่บันทึกไว้เพียงอย่างเดียว:

  • เนื้อหาที่บันทึกไว้ไม่ได้ระบุประเภทของฟิลด์
  • เนื้อหาที่บันทึกไว้ไม่ได้ระบุข้อจำกัดที่ฟิลด์มี (เป็น nullable หรือไม่? เป็นจำนวนเต็มบวกหรือไม่? สตริงนั้นสำหรับอีเมลหรือ URL?)
  • ฟิลด์ที่ nullable อาจมีค่าเริ่มต้น ซึ่งจะไม่ปรากฏในเนื้อหาที่บันทึกไว้

อย่างไรก็ตาม การใช้กลยุทธ์ COPE และประเภท Block เดียวเพื่อแทนบล็อกทั้งหมด Gato GraphQL สามารถสร้างการรวมงานที่ดีมากกับ Gutenberg ที่เอาชนะข้อจำกัดที่มีอยู่ได้

ฉันจะอธิบายตลอดบทความนี้

การรวม Gato GraphQL กับ Gutenberg

โซลูชันนี้อยู่ระหว่างการพัฒนา แต่ฉันสามารถอธิบายได้แล้วว่ามันจะทำงานอย่างไร

แทนที่จะพึ่งพาประเภทที่แตกต่างกันสำหรับแต่ละบล็อก (เหมือนที่ WPGraphQL ทำเมื่อพึ่งพาปลั๊กอิน WPGraphQL for Gutenberg) Gato GraphQL จะจัดเตรียมประเภท Block เดียวเพื่อแทนบล็อกทั้งหมด

ใน queries นี้ ฟิลด์ Post.blockDataItems จะดึงรายการของ Block elements จากโพสต์ (สำหรับบล็อก Gutenberg ต่างๆ รวมถึงย่อหน้า รูปภาพ รายการ และอื่นๆ):

{
  post(by: { id: 1499 }) {
    title
    blockDataItems
  }
}

หากต้องการดึงข้อมูลสำหรับบล็อกเฉพาะ เราสามารถกรองตามชื่อของบล็อก (core/paragraph, core/quote เป็นต้น)

ใน queries นี้ เราดึงเฉพาะบล็อกรูปภาพ:

{
  post(by: { id: 1177 }) {
    title
    blockDataItems(
      filterBy: { include: "core/image" }
    )
  }
}

การตรวจสอบประเภท Block เดียว

ด้วยแนวทางนี้ การตอบสนองสามารถแตกต่างกันขึ้นอยู่กับเนื้อหาที่บันทึกไว้ ไม่ใช่สคีมา คุณสมบัตินี้ทั้งเป็นข้อดี (เนื่องจากทำให้ API มีความยืดหยุ่น) และข้อเสีย (เราไม่สามารถบังคับใช้สัญญาระหว่างเซิร์ฟเวอร์กับไคลเอนต์ได้)

ทุก Block element มีสอง properties:

  • name: ชื่อของบล็อก (core/paragraph, core/quote เป็นต้น)
  • meta: metadata ที่บรรจุอยู่ในบล็อก

บล็อก Gutenberg แต่ละอันต่างกัน บรรจุข้อมูลที่ต่างกัน (เนื้อหาย่อหน้า วิดีโอ YouTube URL แหล่งที่มาของรูปภาพและขนาด เป็นต้น) ดังนั้นข้อมูลที่บรรจุในการตอบสนองสำหรับฟิลด์ meta ก็จะต่างกันด้วย

ดังนั้น ฟิลด์ meta จึงถูกแมปเพียงแค่เป็น JSON object (ซึ่งสามารถบรรจุข้อมูล "ดิบ") ผ่านประเภท JSONObject ที่สอดคล้องกันในสคีมา GraphQL

มันสร้างการตอบสนองนี้:

{
  "data": {
    "post": {
      "title": "COPE with WordPress: Post demo containing plenty of blocks",
      "blockDataItems": [
        {
          "name": "core/paragraph",
          "attributes": {
            "content": "Lorem ipsum dolor sit amet"
          }
        },
        {
          "name": "core/image",
          "attributes": {
            "src": "https://ps.w.org/gutenberg/assets/banner-1544x500.jpg"
          }
        },
        {
          "name": "core/quote",
          "attributes": {
            "quote": "Etiam tempor orci eu lobortis elementum nibh tellus molestie",
            "cite": "Aristoteles"
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "size": "xl",
            "heading": "Welcome to my site"
          }
        },
        {
          "name": "core/list",
          "attributes": {
            "items": [
              "First element",
              "Second element",
              "Third element"
            ]
          }
        },
      ]
    }
  }
}

อย่างที่เราเห็น บล็อกต่างๆ ดึง properties ที่ต่างกัน:

  • core/paragraph มี property content
  • core/image มี property src และ properties เสริม width, height และ caption (ไม่ปรากฏในการตอบสนองข้างต้น)
  • core/quote มี properties quote และ cite (สำหรับบุคคลที่ถูกอ้างถึง)
  • core/heading มี properties header และ size (ค่า xl แทน <h2> เพราะ COPE แยกค่าออกจากแอปพลิเคชันเป้าหมาย ในกรณีนี้คือเว็บไซต์)
  • core/list มี property items ซึ่งเป็นรายการของ elements

เหตุใดประเภท JSONObject ไม่อยู่ในสเปค

ประเภท JSONObject ที่ฉันอธิบายข้างต้นทำให้ GraphQL สามารถดึงฟิลด์ "dynamic" (เช่น ฟิลด์ที่เราไม่รู้จัก) หรือฟิลด์ที่สามารถมีการกำหนดค่าหลายแบบ (อาจเป็นกรณีของบล็อก Gutenberg)

ทีนี้ GraphQL spec ในปัจจุบันไม่รองรับประเภท JSONObject หรือ Map การเพิ่มการรองรับได้ถูกร้องขอ ด้วยเหตุผลเช่น:

[...] การขาดคุณสมบัตินี้เป็นปัญหาที่รุนแรงเป็นพิเศษ เพราะมันได้รับการรองรับในระบบประเภทและบริการมากมายที่ GraphQL เชื่อมต่อด้วย

ส่งผลให้ต้องใช้งาน custom resolvers ในเซิร์ฟเวอร์ ตามด้วย custom transforms ในไคลเอนต์ เพื่อจัดการกับสถานการณ์ที่เซิร์ฟเวอร์ส่ง Map และไคลเอนต์ต้องการ Map แต่ GraphQL อยู่ตรงกลางโดยไม่มีการรองรับ Map ใช่ มันเป็นไปได้ และฉันก็ได้ทำแล้ว แต่มัน boilerplate และ abstraction ที่ค่อนข้างมาก ซึ่งดูเหมือนจะขัดจุดประสงค์ของ__การเขียน API spec ใน GraphQL__

คุณสมบัตินี้ไม่ได้รับการรองรับโดยสเปคเพราะการจัดการกับฟิลด์ dynamic ขัดกับพฤติกรรมการกำหนดประเภทแบบเข้มงวดของ GraphQL ซึ่งทำลายสัญญาระหว่างเซิร์ฟเวอร์และไคลเอนต์

อย่างไรก็ตาม ประเภทนี้สามารถเป็นประโยชน์สำหรับ Gutenberg ดังที่ฉันจะแสดงให้เห็นในภายหลัง

ปัญหาเมื่อใช้ประเภทที่แตกต่างกันสำหรับแต่ละบล็อก และ registry ฝั่งเซิร์ฟเวอร์

หากสร้างประเภท GraphQL ใหม่สำหรับแต่ละบล็อก ปลั๊กอินทั้งหมดจะต้องเพิ่มบล็อกของตนเข้าในสคีมา GraphQL ซึ่งอาจสำเร็จได้โดยอัตโนมัติโดยให้บล็อกทั้งหมดกำหนด properties ของตนใน proposed new server-side registry

หากไม่ทำเช่นนั้น บล็อกเหล่านั้นจะไม่สามารถใช้งานได้ผ่าน API และอาจมีผลกระทบเพิ่มเติม ในบางกรณี เนื้อหาทั้งหมดของโพสต์ที่ถูก query อาจกลายเป็นสิ่งที่ไม่น่าเชื่อถือ

กรณีนี้อาจเกิดขึ้นเมื่อ GraphQL โต้ตอบกับบริการ cloud ภายนอก ซึ่งใช้ฟังก์ชันบางอย่างกับบล็อกทั้งหมดในโพสต์ (คิดถึงการแปลภาษา การแก้ไขไวยากรณ์ คำแนะนำ SEO การวิเคราะห์ เป็นต้น)

มาดูตัวอย่างนี้กัน

เนื่องจากความสามารถหลายภาษาจะถูกเพิ่มใน Gutenberg ในเฟส 4 มาสร้างโมเดลวิธีการแปลบล็อกทั้งหมดในปลั๊กอิน ผ่านการเรียก Google Translate API ที่ดำเนินการผ่านคำสั่ง @strTranslate

(หลังจากการแปลแบบ API เบื้องต้นนี้แล้ว ผู้ใช้สามารถแก้ไขโพสต์บล็อกต่อได้ในภาษาที่แปลแล้ว ภายใน WordPress editor ได้ตลอดเวลา)

บล็อกต่างๆ บรรจุข้อมูลที่แตกต่างกันซึ่งต้องแปล:

  • core/paragraph: ข้อความ
  • core/image: คำอธิบาย
  • core/quote: คำอ้างอิง และบุคคลที่ถูกอ้างถึง (เนื่องจากอาจเป็นตำแหน่งของบุคคล เช่น "ผู้อำนวยการโรงเรียน")
  • core/heading: หัวข้อ
  • core/list: รายการทั้งหมดในรายการ

การใช้ประเภทที่แตกต่างกันสำหรับแต่ละบล็อก queries ที่ได้อาจเป็นแบบนี้:

{
  post(by: { id: 1 }) {
    blocks {
      ... on CoreParagraphBlock {
        content @strTranslate
      }
      ... on CoreImageBlock {
        caption @strTranslate
      }
      ... on CoreQuoteBlock {
        quote @strTranslate
        cite @strTranslate
      }
      ... on CoreHeadingBlock {
        heading @strTranslate
      }
      ... on CoreListBlock {
        items @strTranslateList
      }
      ... on EmbedTwitterBlock {
        caption @strTranslate
      }
      ... on EmbedYoutubeBlock {
        caption @strTranslate
      }
      ... on EmbedVimeoBlock {
        caption @strTranslate
      }
    }
  }
}

และต่อไปเรื่อยๆ ยิ่งมีบล็อกมากเท่าไหร่ queries นี้ก็จะยิ่งยาวขึ้น อาจครอบคลุมถึงร้อยบรรทัดหรือมากกว่านั้น

ปัญหาที่ชัดเจนคือ queries กลายเป็นสัตว์ร้ายที่เราต้องดูแล

นอกจากนี้ เราต้องนำเสนอฟังก์ชันพิเศษเพื่อให้มันทำงานได้กับทุกบล็อก ตัวอย่างเช่น @strTranslate ไม่ทำงานกับ CoreListBlock.items ซึ่งส่งคืนรายการของสตริง (กล่าวคือ มันส่งคืน [String] ในขณะที่คำสั่งคาดว่าจะเป็น String) ดังนั้นเราต้องสร้าง @strTranslateList

และ core/table จะต้องการคำสั่งพิเศษของตัวเอง (@strTranslateTable?)

และบล็อกของ third-party อาจต้องการคำสั่งพิเศษของตัวเองด้วย

และฉันเห็นปัญหาเพิ่มอีกสองข้อ

ทั้งหมดหรือไม่มีเลย

โพสต์บล็อกอาจบรรจุบล็อกใดก็ได้ที่ติดตั้งใน WordPress editor และเราไม่รู้ล่วงหน้า (เมื่อเขียนโค้ด queries) ว่าโพสต์ใช้บล็อกอะไรบ้าง

ดังนั้น ที่ประเภทต่อบล็อก จำนวนประเภทที่ต้องจัดการใน queries จะไม่เทียบเท่ากับจำนวนบล็อกในโพสต์ แต่จะเทียบเท่ากับจำนวนบล็อกที่ติดตั้งใน WordPress editor

จะเกิดอะไรขึ้นถ้าเรามี 100 บล็อกในไซต์ ทั้งจาก WordPress core และปลั๊กอิน? เราต้องการ 100 ประเภทที่แมปกับสคีมา GraphQL บล็อกเพียงอันเดียวที่ไม่ได้แมปสามารถทำลาย "content contract" ส่งผลให้บางบล็อกถูกแปลจากภาษาอังกฤษเป็นภาษาฝรั่งเศส ในขณะที่บล็อกอื่นยังคงเป็นภาษาอังกฤษ

ผลที่ตามมาคือ เราจะไม่สามารถไว้วางใจโพสต์ที่แปลแล้วอีกต่อไป ไม่ว่าจะมีบล็อกที่มีปัญหาหรือไม่ ดังนั้น หากบล็อกทั้งหมดไม่ได้ถูกเพิ่มใน registry แอปพลิเคชันอาจกลายเป็นสิ่งที่ไม่น่าเชื่อถือ

ต้องอัปเดต queries ทุกครั้งที่ติดตั้งบล็อกใหม่

ในทำนองเดียวกัน ทุกบล็อกต้องได้รับการจัดการใน GraphQL queries ซึ่งหมายความว่า เมื่อใดก็ตามที่ติดตั้งบล็อกใหม่ เราต้องกลับไปที่โค้ดแอปพลิเคชัน อัปเดต และ deploy ใหม่

นี่ไม่ใช่แค่ขั้นตอนพิเศษ: เราจะไม่สามารถติดตั้งบล็อกบนไซต์ที่ใช้งานจริงได้โดยไม่กังวลว่าจะทำให้แอปพลิเคชันเสียหาย (จนกว่า queries ทั้งหมดจะได้รับการอัปเดต)

GraphQL ต้องรับใช้ WordPress ไม่ใช่ในทางกลับกัน

คิดอีกครั้งถึงเหตุผลที่ JSONObject ไม่ถูกเพิ่มในสเปค GraphQL นั่นคือเพราะมันไม่เหมาะกับวิธีการทำสิ่งต่างๆ ของ GraphQL

อย่างไรก็ตาม ที่นี่เราไม่ได้สนใจ GraphQL จริงๆ เราสนใจแค่ WordPress และโดยเฉพาะในกรณีนี้คือ Gutenberg

เมื่อรวม GraphQL กับ Gutenberg GraphQL จะทำงานภายในบริบทของ WordPress ซึ่งหมายความว่า WordPress จะต้องตอบสนองข้อกำหนดจาก GraphQL แต่ที่สำคัญกว่านั้นคือ GraphQL ต้องตอบสนองข้อกำหนดจาก WordPress

และในกรณีที่มีความขัดแย้ง WordPress มีความสำคัญเป็นอันดับแรก

หากฟีเจอร์หนึ่งไม่เหมาะกับ GraphQL แต่เหมาะกับ Gutenberg ควรพิจารณาหรือไม่?

ฉันคิดว่าควร

มาดูวิธีที่ประเภท Block เดียวสามารถรับใช้ Gutenberg ได้ดียิ่งขึ้น

การแก้ปัญหาก่อนหน้าผ่านประเภท Block เดียว

ตามตัวอย่างก่อนหน้า การแปลบล็อกทั้งหมดในโพสต์จากภาษาอังกฤษเป็นภาษาฝรั่งเศส โดยใช้ประเภท Block เดียว จะทำแบบนี้ (หรือบางอย่างประมาณแนวคิดนี้):

{
  post(by: { id: 1 }) {
    blocks {
      name
      meta
        @advancePointersInArray(paths: "{{ translatablePaths }}")
          @underEachArrayItem
            @strTranslate(from: "en", to: "fr")
    }
  }
}

แค่นั้นหรือ? queries ทั้งหมด? เพื่อแปลบล็อกทั้งหมด? ใช่

จะใช้งานได้กับบล็อกทั้งหมด ทั้งจาก core และปลั๊กอิน ที่มีอยู่แล้วหรือที่จะสร้างในอนาคต? ใช่

queries นี้ดูแปลกใจไปหน่อยสำหรับคุณหรือเปล่า? ถ้าใช่ นั่นเพราะมันใช้คุณสมบัติ GraphQL ที่ไม่เป็นมาตรฐาน รองรับเฉพาะโดย Gato GraphQL:

  • {{ translatablePaths }} เป็น embeddable field เพื่อใส่ค่าของฟิลด์เป็นอาร์กิวเมนต์ให้กับฟิลด์หรือคำสั่งอื่น (ในกรณีนี้ ประเภท Block จะมีฟิลด์ translatableFields ซึ่งค่าของมันถูกฉีดเข้าไปในคำสั่ง @advancePointersInArray)
  • คำสั่งสามารถถูกประกอบโดยคำสั่งอื่น

ทีนี้ หากฟีเจอร์ตอบสนองสิ่งที่ CMS ต้องการได้พอดี แต่ฟีเจอร์นั้นไม่เป็นมาตรฐาน เราควรใช้มันหรือไม่? ฉันคิดว่าควร

ฉันยังได้ขอให้มีคุณสมบัติเหล่านี้สำหรับสเปค GraphQL ด้วย (แม้ว่าจะไม่ได้รับการยอมรับ):

วิธีการทำงานของประเภท Block เดียว

คำเตือน: ส่วนที่เป็นเทคนิคกำลังจะตามมา

ประเภท Block จะมีฟิลด์ translatablePaths ที่ส่งคืนอาร์เรย์ของ properties จาก JSONObject ที่ต้องแปล:

  • core/paragraph ส่งคืน ["content"]
  • core/image ส่งคืน ["caption"]
  • core/quote ส่งคืน ["quote", "cite"]
  • core/heading ส่งคืน ["header"]
  • core/list ส่งคืน ["items.0", "items.1", "items.2", ...]

@advancePointersInArray คือ meta-directive: มันแก้ไขบริบทสำหรับคำสั่งถัดไป มันทำให้คำสั่งถัดไปได้รับ sub-element จากภายใน JSONObject ที่ถูก query เช่น property content จากบล็อกย่อหน้า รายการ paths ได้มาจากฟิลด์ translatablePaths ที่ประเมินบน entity ที่ถูก query เดียวกัน

จากนั้น @underEachArrayItem คือ meta-directive อีกตัว ที่ iterate ผ่านรายการ elements จาก entity ที่ถูก query และส่งการอ้างอิงไปยัง element ที่ iterate ให้กับคำสั่งถัดไป ในกรณีนี้ มันได้รับรายการทั้งหมดของ properties ที่ต้องแปลสำหรับทุก entities แต่ละอันเป็นประเภท String และส่งต่อ String elements แต่ละตัวไปยังส่วนถัดไป

สุดท้าย คำสั่ง @strTranslate รับ element ประเภท String ที่บรรจุอยู่ใน JSONObject และแปลมันทันที ภายใน JSONObject นั้นเอง

โปรดสังเกตว่าโซลูชันนี้มีความยืดหยุ่นเพียงใด การระบุ path ไปยังสตริงภายใน JSONObject เพียงพอที่จะเข้าถึงค่า แก้ไขด้วย @strTranslate (หรือคำสั่งอื่น) และอาจถึงขั้นบันทึกค่าไว้ใน DB อีกครั้ง (งานเพื่อให้บรรลุเป้าหมายนี้กำลังดำเนินการอยู่)

มันใช้งานได้กับ core/list แล้ว เนื่องจาก elements ทั้งหมดในรายการสามารถเข้าถึงได้ภายใต้ path ของตัวเอง (items.0 คือ element แรกในอาร์เรย์ เป็นต้น) จากนั้นสามารถเข้าถึงค่า String จากแต่ละอัน และส่งต่อไปยัง @strTranslate ดังนั้นจึงไม่จำเป็นต้องสร้าง @strTranslateList

ในทำนองเดียวกัน มันจะใช้งานได้กับ core/table ด้วย เราเพียงแค่ต้องเปิดเผยข้อมูลผ่าน property cells ซึ่งจะเป็นอาร์เรย์ 2 มิติ (หนึ่งสำหรับแถว บรรจุหนึ่งสำหรับคอลัมน์) จากนั้น translatablePaths สามารถเข้าถึง elements ทั้งหมดได้เป็น ["cells.0.0", "cells.0.1", "cells.1.0", ...]

และมันจะใช้งานได้กับบล็อก third-party ใดก็ได้ด้วย สำหรับสิ่งนั้น เราต้องใส่ใจว่าข้อมูลบล็อกถูกบันทึกอย่างไร และจากนั้นเราสามารถอนุมาน path ไปยัง properties ของมันได้

ประเภท Block เดียวต้องการการกำหนดค่าบนพื้นฐานของโค้ด PHP

การแมปบล็อก เพื่อให้เรารู้ว่าจะหา properties ของ metadata ได้ที่ไหน สามารถทำได้ผ่านการกำหนดค่า ดังนั้นเราจึงสามารถจัดการกับมันได้อย่างยืดหยุ่นมาก

ใน Gutenberg มีสองตำแหน่งที่ property ของบล็อกสามารถบันทึกได้: เป็น attribute หรือภายในเนื้อหาที่ render แล้ว

ตัวอย่างเช่น นี่คือวิธีที่บล็อก core/image ถูกบันทึก:

<!-- wp:image {"id":1670,"sizeSlug":"large","linkDestination":"none"} -->
<figure class="wp-block-image size-large">
<img src="https://newapi.getpop.org/wp/wp-content/uploads/2021/01/dynamic-include-first-query.webp" alt="" class="wp-image-1670"/>
</figure>
<!-- /wp:image -->

ในกรณีนี้ เรามี:

  1. Properties id, sizeSlug และ linkDestination ถูกบันทึกเป็น attributes
  2. Property src ถูกบันทึกภายในเนื้อหาที่ render แล้ว

ทีนี้ เมื่อ query API การตอบสนองสำหรับบล็อก core/image จะเป็นดังนี้:

{
  "data": {
    "blocks": [
      {
        "name": "core/image",
        "meta": {
          "id": 1670,
          "sizeSlug": "large",
          "linkDestination": "none",
          "src": "https://newapi.getpop.org/wp/wp-content/uploads/2021/01/dynamic-include-first-query.webp"
        }
      }
    ]
  }
}

API รู้วิธีดึง properties โดยการ parse บล็อกที่บันทึกไว้ใน Gutenberg (นั่นคือกลยุทธ์ COPE) กระบวนการนี้สามารถทำได้โดยอัตโนมัติในระดับหนึ่ง และจากนั้นต้องการ input ด้วยตนเองผ่าน hooks หรือผ่าน user interface

การรับ properties ที่แมปโดยตรงเป็น attributes นั้นเป็นเรื่องง่าย GraphQL server สามารถดึง attributes ทั้งหมดจากบล็อกและทำให้พร้อมใช้งานเป็น properties ได้แล้ว หรือถ้าต้องการกำหนดอย่างชัดเจนว่าจะเปิดเผยอันไหน ก็สามารถทำได้ผ่าน filter hooks:

$attrs = apply_filters("blockPropsAsAttr:core/image", []);
 
add_filter("blockPropsAsAttr:core/image", function ($attrs) {
  return array_merge($attrs, ['id', 'sizeSlug', 'linkDestination']);
})

Properties ที่บันทึกไว้ในเนื้อหาสามารถดึงออกมาได้ผ่าน regex:

$propRegexes = apply_filters("blockPropsAsRegex:core/image", []);
 
add_filter("blockPropsAsRegex:core/image", function ($propRegexes) {
  $propRegexes['src'] = '/<img src="(.*?)"/';
  return $propRegexes;
})

สุดท้าย เราระบุว่า properties ใดของบล็อกที่แปลได้ เพื่อให้ @strTranslate ทำงานบนมัน:

$propRegexes = apply_filters("translatableProperties:core/image", []);
 
add_filter("translatableProperties:core/image", function ($properties) {
  $properties[] = 'caption';
  return $properties;
})

ทีนี้ properties เหล่านี้ยังคงต้องได้รับการตอบสนองโดยใครบางคน น่าจะเป็นนักพัฒนาปลั๊กอิน ดังนั้น การมี server-side registry จะช่วยให้บรรลุเป้าหมายนี้

แต่ถ้าชุมชน WordPress ไม่ต้องการเพิ่ม server-side registry ที่เสนอ? กลยุทธ์นี้สามารถปรับตัวได้ง่าย เพราะ mapping สามารถทำได้ผ่านโค้ด PHP ดังที่แสดงให้เห็นแล้ว

หากบล็อกใดไม่ได้รับการ map ผู้ใช้ยังสามารถทำได้เอง เพียงแค่รู้เล็กน้อยเกี่ยวกับ Gutenberg และไม่ต้องรู้เรื่อง GraphQL หรือสคีมาเลย

นอกจากนี้ เราสามารถให้ GraphQL แจ้งเตือนผู้ใช้เมื่อมีบล็อกที่ไม่ได้รับการ map (จึงไม่สามารถแปลได้) เราสามารถทำได้โดยการเพิ่ม meta-directive @if ซึ่งหากเงื่อนไขเป็นจริง จะดำเนินการคำสั่ง @sendEmail:

{
  post(by: { id: 1 }) {
    blocks {
      name
      meta
        @advancePointersInArray(paths: "{{ translatablePaths }}")
          @underEachArrayItem
            @strTranslate(from: "en", to: "fr")
        @if(condition: "{{ isTranslatablePathsUnmapped }}")
          @sendEmail(
            to: "{{ root.adminEmail }}",
            subject: "Block with name {{ name }} has 'translatablePaths' unmapped"
          )
    }
  }
}

โซลูชันนี้มีความยืดหยุ่นและเรียบง่าย และทำให้ GraphQL รับใช้ WordPress โดยไม่ต้องการให้นักพัฒนาเรียนรู้เทคโนโลยีใหม่หรือเปลี่ยนแปลงวิธีการทำงานของ Gutenberg

สรุป

เมื่อคิดถึงว่าการรวม GraphQL กับ Gutenberg จะเป็นอย่างไร (จากการรวมเข้าใน WordPress core ที่อาจเกิดขึ้น) เราต้องแน่ใจว่า GraphQL สามารถจัดการกับข้อกำหนดในอนาคตทั้งหมดของ Gutenberg ได้ รวมถึงการรองรับเต็มรูปแบบสำหรับ:

  • บล็อกหลายภาษา
  • Full Site Editing
  • การแก้ไขร่วมกัน
  • การโต้ตอบกับบริการ third-party บนไซต์ที่ใช้งานจริง

ทั้งหมดนี้ต้องทำให้สำเร็จโดยหวังว่าจะไม่จำเป็นต้องเปลี่ยนแปลง Gutenberg (อย่างน้อยก็ไม่ในทางที่มีนัยสำคัญ) และลดงานใหม่ที่ต้องการจากนักพัฒนาปลั๊กอิน

โดยคำนึงถึงสิ่งเหล่านี้ ฉันเชื่อว่าแนวทางที่ 4 ที่ฉันเสนอที่นี่สามารถใช้งานได้ดีจริงๆ


สมัครรับจดหมายข่าวของเรา

ติดตามการอัปเดตทั้งหมดของ Gato GraphQL