การย้ายถิ่นของแฟรงเกนสไตน์: แนวทางกรอบงาน-ไม่เชื่อเรื่องพระเจ้า (ตอนที่ 2)

เผยแพร่แล้ว: 2022-03-10
สรุปโดยย่อ ↬ เมื่อเร็ว ๆ นี้เราได้พูดคุยกันว่า “Frankenstein Migration” คืออะไร เปรียบเทียบกับประเภทการโยกย้ายทั่วไป และกล่าวถึงโครงสร้างหลักสองประการ: microservices และ Web Components นอกจากนี้เรายังมีพื้นฐานทางทฤษฎีว่าการย้ายประเภทนี้ทำงานอย่างไร หากคุณไม่ได้อ่านหรือลืมการสนทนานั้น คุณอาจต้องการกลับไปที่ส่วนที่ 1 ก่อนเพราะจะช่วยให้เข้าใจทุกอย่างที่เราจะกล่าวถึงในส่วนที่สองของบทความนี้

ในบทความนี้ เราจะนำทฤษฎีทั้งหมดมาทดสอบโดยดำเนินการย้ายแอปพลิเคชันทีละขั้นตอน โดยทำตามคำแนะนำจากส่วนก่อนหน้านี้ เพื่อทำให้สิ่งต่าง ๆ ตรงไปตรงมา ลดความไม่แน่นอน ความไม่รู้ และการคาดเดาที่ไม่จำเป็น สำหรับตัวอย่างในทางปฏิบัติของการย้ายถิ่น ฉันตัดสินใจสาธิตวิธีปฏิบัติบนแอปพลิเคชันสิ่งที่ต้องทำอย่างง่าย

ได้เวลาทดสอบทฤษฎีแล้ว
ได้เวลาทดสอบทฤษฎีแล้ว (ตัวอย่างขนาดใหญ่)

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

มุมมองเริ่มต้นของแอปพลิเคชัน TodoMVC
มุมมองเริ่มต้นของแอปพลิเคชัน TodoMVC (ตัวอย่างขนาดใหญ่)

สำหรับบทความนี้ เป็นจุดเริ่มต้น ฉันเลือกแอปพลิเคชัน jQuery จากโครงการ TodoMVC — ตัวอย่างที่หลายคนอาจคุ้นเคยอยู่แล้ว jQuery เป็นมรดกที่เพียงพอ อาจสะท้อนถึงสถานการณ์จริงกับโครงการของคุณ และที่สำคัญที่สุดคือต้องมีการบำรุงรักษาและการแฮ็กที่สำคัญสำหรับการขับเคลื่อนแอปพลิเคชันแบบไดนามิกที่ทันสมัย (ซึ่งน่าจะเพียงพอสำหรับการพิจารณาการย้ายถิ่นไปสู่สิ่งที่มีความยืดหยุ่นมากขึ้น)

อะไรคือ "ความยืดหยุ่น" มากขึ้นที่เราจะย้ายไปในตอนนั้น? เพื่อแสดงกรณีที่ใช้งานได้จริงซึ่งเป็นประโยชน์ในชีวิตจริง ฉันต้องเลือกเฟรมเวิร์กที่ได้รับความนิยมสูงสุดสองแบบในทุกวันนี้: React และ Vue อย่างไรก็ตาม ไม่ว่าฉันจะเลือกอะไร เราจะพลาดบางแง่มุมของทิศทางอื่น

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

ดังนั้น ในส่วนนี้ เราจะดำเนินการผ่านทั้งสองสิ่งต่อไปนี้:

  • การโยกย้ายแอปพลิเคชัน jQuery ไปยัง React และ
  • การโยกย้ายแอปพลิเคชัน jQuery ไปยัง Vue
เป้าหมายของเรา: ผลลัพธ์ของการโยกย้ายไปยัง React และ Vue
เป้าหมายของเรา: ผลลัพธ์ของการโยกย้ายไปยัง React และ Vue (ตัวอย่างขนาดใหญ่)

ที่เก็บรหัส

รหัสทั้งหมดที่กล่าวถึงในที่นี้เปิดเผยต่อสาธารณะ และคุณสามารถเข้าถึงได้ทุกเมื่อที่ต้องการ มีที่เก็บสองแห่งให้คุณเล่นด้วย:

  • Frankenstein TodoMVC
    ที่เก็บนี้มี แอปพลิเคชัน TodoMVC ในเฟรมเวิร์ก/ไลบรารีต่างๆ ตัวอย่างเช่น คุณสามารถค้นหาสาขาต่างๆ เช่น vue , angularjs , react และ jquery ในที่เก็บนี้
  • การสาธิตแฟรงเกนสไตน์
    ประกอบด้วยสาขาต่างๆ ซึ่งแต่ละสาขาแสดงถึง ทิศทาง การย้ายข้อมูลระหว่างแอปพลิเคชัน ซึ่งมีอยู่ในที่เก็บแรก มีสาขาต่างๆ เช่น migration/jquery-to-react และ migration/jquery-to-vue โดยเฉพาะอย่างยิ่งที่เราจะกล่าวถึงในภายหลัง

ที่เก็บทั้งสองอยู่ในระหว่างดำเนินการ และควรเพิ่มสาขาใหม่ที่มีแอปพลิเคชันใหม่และทิศทางการย้ายข้อมูลเป็นประจำ ( คุณมีส่วนร่วมได้ฟรีด้วย! ) ประวัติ Commits ในสาขาการโยกย้ายมีโครงสร้างที่ดีและอาจใช้เป็นเอกสารเพิ่มเติมที่มีรายละเอียดมากกว่าที่ฉันจะกล่าวถึงในบทความนี้

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

  • โคลนสาขา jquery จากที่เก็บ Frankenstein TodoMVC และปฏิบัติตามคำแนะนำด้านล่างทั้งหมดอย่างเคร่งครัด
  • หรือคุณสามารถเปิดสาขาเฉพาะสำหรับการโยกย้ายไปยัง React หรือการโยกย้ายไปยัง Vue จากที่เก็บ Frankenstein Demo และติดตามพร้อมกับประวัติการคอมมิต
  • อีกทางหนึ่ง คุณสามารถผ่อนคลายและอ่านต่อไปได้ เพราะฉันจะเน้นโค้ดที่สำคัญที่สุดที่นี่ และมันสำคัญกว่ามากที่จะเข้าใจกลไกของกระบวนการมากกว่าที่จะเข้าใจโค้ดจริง

ฉันอยากจะพูดอีกครั้งว่าเราจะปฏิบัติตามขั้นตอนที่นำเสนอในส่วนแรกของบทความตามทฤษฎีอย่างเคร่งครัด

มาดำดิ่งกันเลย!

  1. ระบุไมโครเซอร์วิส
  2. อนุญาตการเข้าถึงจากโฮสต์สู่เอเลี่ยน
  3. เขียนไมโครเซอร์วิส/ส่วนประกอบต่างด้าว
  4. เขียน Web Component Wrapper รอบ Alien Service
  5. แทนที่บริการโฮสต์ด้วยส่วนประกอบเว็บ
  6. ล้างและทำซ้ำสำหรับส่วนประกอบทั้งหมดของคุณ
  7. เปลี่ยนไปใช้เอเลี่ยน

1. ระบุไมโครเซอร์วิส

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

ดังนั้น เพื่อให้เห็นกระบวนการของ Frankenstein Migration โดยละเอียดยิ่งขึ้น เราสามารถก้าวไปอีกขั้นและแยกแอปพลิเคชันสิ่งที่ต้องทำนี้ออกเป็นไมโครเซอร์วิสอิสระสองแห่ง:

  1. ช่องป้อนข้อมูลสำหรับเพิ่มรายการใหม่
    บริการนี้ยังสามารถมีส่วนหัวของแอปพลิเคชันโดยพิจารณาจากระยะใกล้ขององค์ประกอบเหล่านี้เท่านั้น
  2. รายการของที่เพิ่มไว้แล้ว
    บริการนี้มีความก้าวหน้ามากกว่า และเมื่อรวมกับตัวรายการแล้ว ก็ยังมีการดำเนินการต่างๆ เช่น การกรอง การดำเนินการของรายการ และอื่นๆ
แอปพลิเคชัน TodoMVC แบ่งออกเป็นสองไมโครเซอร์วิสอิสระ
แอปพลิเคชัน TodoMVC แบ่งออกเป็นสองไมโครเซอร์วิสอิสระ (ตัวอย่างขนาดใหญ่)

เคล็ดลับ : หากต้องการตรวจสอบว่าบริการที่เลือกเป็นอิสระจากกันหรือไม่ ให้ลบมาร์กอัป HTML ที่แสดงแต่ละบริการเหล่านี้ ตรวจสอบให้แน่ใจว่าฟังก์ชันที่เหลือยังคงใช้งานได้ ในกรณีของเรา ควรเพิ่มรายการใหม่ลงใน localStorage (ที่แอปพลิเคชันนี้ใช้เป็นที่เก็บข้อมูล) จากช่องป้อนข้อมูลโดยไม่มีรายการ ในขณะที่รายการยังคงแสดงผลรายการจาก localStorage แม้ว่าช่องป้อนข้อมูลจะหายไป หากแอปพลิเคชันของคุณแสดงข้อผิดพลาดเมื่อคุณลบมาร์กอัปสำหรับไมโครเซอร์วิสที่อาจเกิดขึ้น ให้ดูที่ส่วน “รีแฟกเตอร์หากจำเป็น” ในส่วนที่ 1 สำหรับตัวอย่างวิธีจัดการกับกรณีดังกล่าว

แน่นอน เราสามารถแยกบริการที่สองและการแสดงรายการเพิ่มเติมออกเป็นไมโครเซอร์วิสอิสระสำหรับแต่ละรายการโดยเฉพาะ อย่างไรก็ตาม อาจละเอียดเกินไปสำหรับตัวอย่างนี้ ดังนั้น สำหรับตอนนี้ เราสรุปได้ว่าแอปพลิเคชันของเราจะมีสองบริการ พวกเขาเป็นอิสระและแต่ละคนทำงานตามภารกิจเฉพาะของตนเอง ดังนั้นเราจึงแยกแอปพลิเคชันของเราออกเป็น ไมโคร เซอร์วิส

2. อนุญาตการเข้าถึงจากโฮสต์สู่เอเลี่ยน

ผมขอเตือนคุณสั้นๆ ว่าสิ่งเหล่านี้คืออะไร

  • เจ้าภาพ
    นี่คือสิ่งที่แอปพลิเคชันปัจจุบันของเราเรียกว่า มันเขียนด้วยกรอบการทำงานที่เรากำลังจะ ย้ายออกไป ในกรณีนี้ แอปพลิเคชัน jQuery ของเรา
  • เอเลี่ยน
    พูดง่ายๆ ว่านี่คือการเขียน Host ใหม่อย่างค่อยเป็นค่อยไปในเฟรมเวิร์กใหม่ที่เรากำลังจะ ย้ายไปที่ . ในกรณีนี้คือแอปพลิเคชัน React หรือ Vue

หลักการง่ายๆ ใน การแยก Host และ Alien ก็คือ คุณควรจะสามารถพัฒนาและปรับใช้สิ่งเหล่านี้ได้โดยไม่ทำลายอีกอันหนึ่ง — ในเวลาใดก็ได้

การรักษาโฮสต์และเอเลี่ยนให้เป็นอิสระจากกันเป็นสิ่งสำคัญสำหรับการโยกย้ายแฟรงเกนสไตน์ อย่างไรก็ตาม สิ่งนี้ทำให้การจัดเตรียมการสื่อสารระหว่างคนทั้งสองมีความท้าทายเล็กน้อย เราจะอนุญาตให้ Host เข้าถึง Alien โดยไม่ต้องแยกทั้งสองเข้าด้วยกันได้อย่างไร?

การเพิ่มเอเลี่ยนเป็นโมดูลย่อยของโฮสต์ของคุณ

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

หลักการทั่วไปของสถาปัตยกรรมโปรเจ็กต์ของเราที่มีโมดูลย่อย git ควรมีลักษณะดังนี้:

  • ทั้งโฮสต์และเอเลี่ยนต่างเป็นอิสระและถูกเก็บไว้ในที่เก็บ git แยกต่างหาก
  • โฮสต์อ้างอิงเอเลี่ยนเป็นโมดูลย่อย ในขั้นตอนนี้ Host จะเลือกสถานะเฉพาะ (commit) ของ Alien และเพิ่มเป็นโฟลเดอร์ย่อยในโครงสร้างโฟลเดอร์ของ Host
เพิ่ม React TodoMVC เป็นโมดูลย่อย git ในแอปพลิเคชัน jQuery TodoMVC
เพิ่ม React TodoMVC เป็นโมดูลย่อย git ในแอปพลิเคชัน jQuery TodoMVC (ตัวอย่างขนาดใหญ่)

ขั้นตอนการเพิ่มโมดูลย่อยจะเหมือนกันสำหรับแอปพลิเคชันใดๆ การสอน git submodules อยู่นอกเหนือขอบเขตของบทความนี้ และไม่เกี่ยวข้องโดยตรงกับ Frankenstein Migration ลองมาดูตัวอย่างที่เป็นไปได้โดยสังเขปกัน

ในตัวอย่างด้านล่าง เราใช้ทิศทางการตอบสนองเป็นตัวอย่าง สำหรับทิศทางการโยกย้ายอื่นๆ ให้แทนที่ react ด้วยชื่อสาขาจาก Frankenstein TodoMVC หรือปรับเป็นค่าที่กำหนดเองตามต้องการ

หากคุณปฏิบัติตามโดยใช้แอปพลิเคชัน jQuery TodoMVC ดั้งเดิม:

 $ git submodule add -b react [email protected]:mishunov/frankenstein-todomvc.git react $ git submodule update --remote $ cd react $ npm i

หากคุณติดตามพร้อมกับ migration/jquery-to-react (หรือทิศทางการโยกย้ายอื่น ๆ ) จากที่เก็บ Frankenstein Demo แอปพลิเคชัน Alien ควรจะอยู่ที่นั่นเป็น git submodule และคุณควรเห็นโฟลเดอร์ที่เกี่ยวข้อง อย่างไรก็ตาม โฟลเดอร์จะว่างเปล่าโดยค่าเริ่มต้น และคุณจำเป็นต้องอัปเดตและเริ่มต้นโมดูลย่อยที่ลงทะเบียนไว้

จากรูทของโปรเจ็กต์ของคุณ (โฮสต์ของคุณ):

 $ git submodule update --init $ cd react $ npm i

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

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

3. เขียนไมโครเซอร์วิส/ส่วนประกอบต่างด้าว

ในขั้นตอนนี้ เราต้องตัดสินใจว่าจะย้าย microservice ใดก่อน และเขียน/ใช้งานในด้านของ Alien มาทำตามลำดับของบริการเดียวกันกับที่เราระบุไว้ในขั้นตอนที่ 1 และเริ่มจากอันแรก: ช่องใส่สำหรับเพิ่มรายการใหม่ อย่างไรก็ตาม ก่อนที่เราจะเริ่มต้น เรามาตกลงกันว่านอกเหนือจากจุดนี้ เราจะใช้ องค์ประกอบ คำที่ดีกว่าแทนที่จะเป็น ไมโคร เซอร์วิสหรือ บริการ เนื่องจากเรากำลังก้าวไปสู่สถานที่ตั้งของเฟรมเวิร์กส่วนหน้าและ องค์ประกอบ คำว่าตามคำจำกัดความที่ทันสมัย กรอบ.

สาขาของที่เก็บ Frankenstein TodoMVC มีส่วนประกอบที่เป็นผลลัพธ์ซึ่งแสดงถึงบริการแรก "ฟิลด์อินพุตสำหรับการเพิ่มรายการใหม่" เป็นส่วนประกอบส่วนหัว:

  • องค์ประกอบส่วนหัวใน React
  • องค์ประกอบส่วนหัวใน Vue

การเขียนส่วนประกอบในกรอบงานที่คุณเลือกอยู่นอกเหนือขอบเขตของบทความนี้ และไม่ได้เป็นส่วนหนึ่งของ Frankenstein Migration อย่างไรก็ตาม มีสองสิ่งที่ควรคำนึงถึงขณะเขียนองค์ประกอบเอเลี่ยน

อิสรภาพ

ประการแรก ส่วนประกอบใน Alien ควรเป็นไปตามหลักการของความเป็นอิสระเดียวกัน ซึ่งก่อนหน้านี้ได้ตั้งค่าไว้ที่ฝั่งของโฮสต์: ส่วนประกอบต่างๆ ไม่ควรพึ่งพาส่วนประกอบอื่นๆ แต่อย่างใด

การทำงานร่วมกัน

เนื่องด้วยความเป็นอิสระของบริการ ส่วนประกอบส่วนใหญ่ในโฮสต์ของคุณอาจสื่อสารในลักษณะที่เป็นที่ยอมรับ ไม่ว่าจะเป็นระบบการจัดการสถานะ การสื่อสารผ่านที่เก็บข้อมูลที่ใช้ร่วมกันบางส่วน หรือโดยตรงผ่านระบบเหตุการณ์ DOM “การทำงานร่วมกัน” ของส่วนประกอบต่างด้าวหมายความว่าพวกเขาควรจะสามารถเชื่อมต่อกับแหล่งการสื่อสารเดียวกันซึ่งก่อตั้งโดย Host เพื่อส่งข้อมูลเกี่ยวกับการเปลี่ยนแปลงสถานะของมันและรับฟังการเปลี่ยนแปลงในส่วนประกอบอื่น ๆ ในทางปฏิบัติ นี่หมายความว่าหากส่วนประกอบในโฮสต์ของคุณสื่อสารผ่านเหตุการณ์ DOM การสร้างส่วนประกอบ Alien ของคุณโดยเฉพาะโดยคำนึงถึงการจัดการสถานะจะไม่ทำงานอย่างไม่มีที่ติสำหรับการย้ายประเภทนี้ แต่น่าเสียดาย

ตัวอย่างเช่น ลองดูไฟล์ js/storage.js ที่เป็นช่องทางการสื่อสารหลักสำหรับส่วนประกอบ jQuery ของเรา:

 ... fetch: function() { return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]"); }, save: function(todos) { localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); var event = new CustomEvent("store-update", { detail: { todos } }); document.dispatchEvent(event); }, ...

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

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

 import todoStorage from "../storage"; class Header extends Component { constructor(props) { this.state = { todos: todoStorage.fetch() }; } componentDidMount() { document.addEventListener("store-update", this.updateTodos); } componentWillUnmount() { document.removeEventListener("store-update", this.updateTodos); } componentDidUpdate(prevProps, prevState) { if (prevState.todos !== this.state.todos) { todoStorage.save(this.state.todos); } } ... }

ตอนนี้ ส่วนประกอบ Alien ของเราสามารถพูดภาษาเดียวกันกับส่วนประกอบ Host และในทางกลับกัน

4. เขียน Web Component Wrapper รอบ Alien Service

แม้ว่าตอนนี้เราจะอยู่ในขั้นตอนที่สี่เท่านั้น แต่เราประสบความสำเร็จค่อนข้างมาก:

  • เราได้แยกแอปพลิเคชันโฮสต์ของเราออกเป็นบริการอิสระซึ่งพร้อมที่จะแทนที่ด้วยบริการต่างด้าว
  • เราได้ตั้งค่าโฮสต์และเอเลี่ยนให้เป็นอิสระจากกันโดยสิ้นเชิง แต่เชื่อมต่อกันเป็นอย่างดีผ่าน git submodules
  • เราได้เขียนองค์ประกอบ Alien แรกของเราโดยใช้เฟรมเวิร์กใหม่

ตอนนี้ได้เวลาสร้างสะพานเชื่อมระหว่าง Host และ Alien เพื่อให้ส่วนประกอบ Alien ใหม่สามารถทำงานได้ใน Host

คำ เตือนจากส่วนที่ 1 : ตรวจสอบให้แน่ใจว่าโฮสต์ของคุณมีแพ็คเกจรวมที่พร้อมใช้งาน ในบทความนี้ เราใช้ Webpack แต่ไม่ได้หมายความว่าเทคนิคนี้ใช้ไม่ได้กับ Rollup หรือ Bundler อื่นๆ ที่คุณเลือก อย่างไรก็ตาม ฉันปล่อยให้การทำแผนที่จาก Webpack ไปที่การทดลองของคุณ

อนุสัญญาการตั้งชื่อ

ตามที่กล่าวไว้ในบทความที่แล้ว เราจะใช้ Web Components เพื่อรวม Alien เข้ากับ Host ทางฝั่ง Host เราสร้างไฟล์ใหม่: js/frankenstein-wrappers/Header-wrapper.js (มันจะเป็นเครื่องห่อหุ้มแฟรงเกนสไตน์เครื่องแรกของเรา) พึงระลึกไว้เสมอว่าควรตั้งชื่อ wrapper ของคุณให้เหมือนกับส่วนประกอบของคุณในแอปพลิเคชัน Alien เช่น เพียงเพิ่มส่วนต่อท้าย “ -wrapper ” คุณจะเห็นในภายหลังว่าเหตุใดจึงเป็นความคิดที่ดี แต่สำหรับตอนนี้ ให้ตกลงกันว่านี่หมายความว่าหากองค์ประกอบ Alien ถูกเรียกว่า Header.js (ใน React) หรือ Header.vue (ใน Vue) ตัวห่อหุ้มที่เกี่ยวข้องบน ฝั่งโฮสต์ควรเรียกว่า Header-wrapper.js

ใน wrapper แรกของเรา เราเริ่มต้นด้วยต้นแบบพื้นฐานสำหรับการลงทะเบียนองค์ประกอบที่กำหนดเอง:

 class FrankensteinWrapper extends HTMLElement {} customElements.define("frankenstein-header-wrapper", FrankensteinWrapper);

ต่อไป เราต้องเริ่มต้น Shadow DOM สำหรับองค์ประกอบนี้

โปรดอ้างอิงถึงส่วนที่ 1 เพื่อรับเหตุผลว่าทำไมเราจึงใช้ Shadow DOM

 class FrankensteinWrapper extends HTMLElement { connectedCallback() { this.attachShadow({ mode: "open" }); } }

ด้วยเหตุนี้ เรามีส่วนสำคัญทั้งหมดของ Web Component และถึงเวลาที่จะเพิ่มองค์ประกอบ Alien ของเราลงในมิกซ์ ก่อนอื่น ในตอนต้นของกระดาษห่อหุ้มแฟรงเกนสไตน์ เราควรนำเข้าบิตทั้งหมดที่เกี่ยวข้องกับการเรนเดอร์ส่วนประกอบเอเลี่ยน

 import React from "../../react/node_modules/react"; import ReactDOM from "../../react/node_modules/react-dom"; import HeaderApp from "../../react/src/components/Header"; ...

ที่นี่เราต้องหยุดสักครู่ โปรดทราบว่าเราไม่นำเข้าการพึ่งพาของ Alien จาก node_modules ของ Host ทุกอย่างมาจากตัวเอเลี่ยนที่อยู่ในโฟลเดอร์ react/ ย่อย นั่นคือเหตุผลที่ขั้นตอนที่ 2 มีความสำคัญมาก และจำเป็นต้องตรวจสอบให้แน่ใจว่าโฮสต์สามารถเข้าถึงทรัพย์สินของเอเลี่ยนได้อย่างเต็มที่

ตอนนี้ เราสามารถแสดงองค์ประกอบ Alien ของเราภายใน Shadow DOM ของ Web Component:

 ... connectedCallback() { ... ReactDOM.render(<HeaderApp />, this.shadowRoot); } ...

หมายเหตุ : ในกรณีนี้ React ไม่ต้องการสิ่งอื่นใด อย่างไรก็ตาม ในการเรนเดอร์องค์ประกอบ Vue คุณต้องเพิ่มโหนดการตัดคำเพื่อให้มีองค์ประกอบ Vue ของคุณดังต่อไปนี้:

 ... connectedCallback() { const mountPoint = document.createElement("div"); this.attachShadow({ mode: "open" }).appendChild(mountPoint); new Vue({ render: h => h(VueHeader) }).$mount(mountPoint); } ...

สาเหตุของสิ่งนี้คือความแตกต่างในวิธีที่ส่วนประกอบการแสดงผล React และ Vue: React ผนวกส่วนประกอบเข้ากับโหนด DOM ที่อ้างอิง ในขณะที่ Vue แทนที่โหนด DOM ที่อ้างอิงด้วยส่วนประกอบ ดังนั้น หากเราทำ .$mount(this.shadowRoot) สำหรับ Vue มันจะแทนที่ Shadow DOM

นั่นคือทั้งหมดที่เราต้องทำกับเครื่องห่อของเราในตอนนี้ ผลลัพธ์ปัจจุบันสำหรับตัวห่อหุ้ม Frankenstein ทั้งในทิศทางการย้าย jQuery-to-React และ jQuery-to-Vue สามารถพบได้ที่นี่:

  • Frankenstein Wrapper สำหรับส่วนประกอบ React
  • Frankenstein Wrapper สำหรับส่วนประกอบ Vue

เพื่อสรุปกลไกของกระดาษห่อหุ้มแฟรงเกนสไตน์:

  1. สร้างองค์ประกอบที่กำหนดเอง
  2. เริ่มต้น Shadow DOM
  3. นำเข้าทุกอย่างที่จำเป็นสำหรับการเรนเดอร์องค์ประกอบเอเลี่ยน
  4. แสดงองค์ประกอบ Alien ภายใน Shadow DOM ขององค์ประกอบที่กำหนดเอง

อย่างไรก็ตาม สิ่งนี้ไม่ได้ทำให้ Alien ของเราอยู่ในโฮสต์โดยอัตโนมัติ เราต้องแทนที่มาร์กอัปโฮสต์ที่มีอยู่ด้วยกระดาษห่อหุ้มแฟรงเกนสไตน์ใหม่ของเรา

คาดเข็มขัดนิรภัย อาจไม่ง่ายอย่างที่คิด!

5. แทนที่บริการโฮสต์ด้วยส่วนประกอบเว็บ

ไปต่อและเพิ่มไฟล์ Header-wrapper.js ใหม่ของเราไปที่ index.html และแทนที่มาร์กอัปส่วนหัวที่มีอยู่ด้วยองค์ประกอบที่กำหนดเอง <frankenstein-header-wrapper> ที่สร้างขึ้นใหม่

 ... <!-- <header class="header">--> <!-- <h1>todos</h1>--> <!-- <input class="new-todo" placeholder="What needs to be done?" autofocus>--> <!-- </header>--> <frankenstein-header-wrapper></frankenstein-header-wrapper> ... <script type="module" src="js/frankenstein-wrappers/Header-wrapper.js"></script>

น่าเสียดาย วิธีนี้ใช้ไม่ได้ผลง่ายๆ อย่างนั้น หากคุณเปิดเบราว์เซอร์และตรวจสอบคอนโซล มี Uncaught SyntaxError รอคุณอยู่ ขึ้นอยู่กับเบราว์เซอร์และการรองรับโมดูล ES6 ซึ่งอาจเกี่ยวข้องกับการนำเข้า ES6 หรือวิธีการแสดงองค์ประกอบ Alien ไม่ว่าจะด้วยวิธีใด เราต้องทำอะไรบางอย่างเกี่ยวกับเรื่องนี้ แต่ปัญหาและวิธีแก้ไขควรเป็นที่คุ้นเคยและชัดเจนสำหรับผู้อ่านส่วนใหญ่

5.1. อัปเดต Webpack และ Babel เมื่อจำเป็น

เราควรจะใช้เวทย์มนตร์ Webpack และ Babel ก่อนรวมเครื่องห่อ Frankenstein ของเรา การโต้เถียงกับเครื่องมือเหล่านี้อยู่นอกเหนือขอบเขตของบทความ แต่คุณสามารถดูการกระทำที่เกี่ยวข้องได้ในที่เก็บ Frankenstein Demo:

  • การกำหนดค่าสำหรับการโยกย้ายไปยัง React
  • การกำหนดค่าสำหรับการโยกย้ายไปยัง Vue

โดยพื้นฐานแล้ว เราตั้งค่าการประมวลผลไฟล์รวมถึง จุดเริ่มต้นใหม่ frankenstein ในการกำหนดค่าของ Webpack เพื่อให้มีทุกอย่างที่เกี่ยวข้องกับเครื่องห่อ Frankenstein ไว้ในที่เดียว

เมื่อ Webpack ใน Host รู้วิธีประมวลผลส่วนประกอบ Alien และ Web Components เราก็พร้อมที่จะแทนที่มาร์กอัปของ Host ด้วยเครื่องห่อ Frankenstein ใหม่

5.2. การเปลี่ยนชิ้นส่วนจริง

การเปลี่ยนส่วนประกอบควรตรงไปตรงมาในตอนนี้ ใน index.html ของโฮสต์ของคุณ ให้ทำดังต่อไปนี้:

  1. แทนที่ <header class="header"> องค์ประกอบ DOM ด้วย <frankenstein-header-wrapper> ;
  2. เพิ่มสคริปต์ใหม่ frankenstein.js นี่คือจุดเริ่มต้นใหม่ใน Webpack ที่มีทุกอย่างที่เกี่ยวข้องกับเครื่องห่อ Frankenstein
 ... <!-- We replace <header class="header"> --> <frankenstein-header-wrapper></frankenstein-header-wrapper> ... <script src="./frankenstein.js"></script>

แค่นั้นแหละ! รีสตาร์ทเซิร์ฟเวอร์ของคุณหากจำเป็น และพบกับความมหัศจรรย์ของส่วนประกอบ Alien ที่รวมอยู่ใน Host

อย่างไรก็ตาม มีบางอย่างที่ดูเหมือนจะขาดหายไป คอมโพเนนต์ Alien ในบริบทโฮสต์ดูไม่เหมือนในบริบทของแอปพลิเคชัน Alien แบบสแตนด์อโลน มันไม่มีสไตล์

ส่วนประกอบ Alien React ที่ไม่มีสไตล์หลังจากรวมเข้ากับ Host
ส่วนประกอบ Alien React ที่ไม่มีสไตล์หลังจากรวมเข้ากับ Host (ตัวอย่างขนาดใหญ่)

ทำไมจึงเป็นเช่นนั้น? รูปแบบขององค์ประกอบไม่ควรรวมเข้ากับองค์ประกอบ Alien ใน Host โดยอัตโนมัติหรือไม่ ฉันหวังว่าพวกเขาจะทำได้ แต่ในสถานการณ์ที่มากเกินไป มันขึ้นอยู่กับ เรากำลังเข้าสู่ส่วนที่ท้าทายของ Frankenstein Migration

5.3. ข้อมูลทั่วไปเกี่ยวกับการจัดแต่งทรงผมของส่วนประกอบต่างด้าว

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

สไตล์สากล

เราทุกคนต่างคุ้นเคยกับสิ่งเหล่านี้: สไตล์สากลสามารถ (และมักจะ) กระจายโดยไม่มีองค์ประกอบเฉพาะใด ๆ และนำไปใช้กับทั้งหน้า สไตล์โกลบอลส่งผลต่อโหนด DOM ทั้งหมดด้วยตัวเลือกที่ตรงกัน

ตัวอย่างบางส่วนของสไตล์สากล ได้แก่ แท็ก <style> และ <link rel="stylesheet"> ที่พบใน index.html ของคุณ อีกทางหนึ่ง สามารถนำสไตล์ชีตส่วนกลางเข้าสู่โมดูลรูท JS บางตัวเพื่อให้ส่วนประกอบทั้งหมดสามารถเข้าถึงได้เช่นกัน

ปัญหาของการใส่สไตล์แอปพลิเคชันในลักษณะนี้ชัดเจน: การรักษาสไตล์ชีตแบบเสาหินสำหรับแอปพลิเคชันขนาดใหญ่กลายเป็นเรื่องยากมาก ดังที่เราเห็นในบทความก่อนหน้านี้ สไตล์สากลสามารถทำลายส่วนประกอบที่แสดงผลโดยตรงในแผนผัง DOM หลักได้เช่นเดียวกับใน React หรือ Vue

รวมสไตล์

สไตล์เหล่านี้มักจะประกอบเข้าด้วยกันอย่างแน่นหนากับส่วนประกอบเอง และแทบจะไม่มีการแจกจ่ายโดยไม่มีส่วนประกอบ โดยทั่วไป สไตล์จะอยู่ในไฟล์เดียวกันกับคอมโพเนนต์ ตัวอย่างที่ดีของการจัดสไตล์ประเภทนี้คือองค์ประกอบที่มีสไตล์ในโมดูล React หรือ CSS และ Scoped CSS ในองค์ประกอบไฟล์เดียวใน Vue อย่างไรก็ตาม ไม่ว่าเครื่องมือต่างๆ สำหรับการเขียนสไตล์ที่รวมกลุ่มไว้จะมีเครื่องมือที่หลากหลาย หลักการพื้นฐานในเครื่องมือส่วนใหญ่จะเหมือนกัน นั่นคือ เครื่องมือมีกลไกการกำหนดขอบเขตเพื่อล็อคสไตล์ที่กำหนดไว้ในองค์ประกอบ เพื่อไม่ให้รูปแบบเสียหายกับส่วนประกอบอื่นๆ หรือส่วนรวม สไตล์

เหตุใดรูปแบบที่กำหนดขอบเขตจึงเปราะบางได้

ในส่วนที่ 1 เมื่อพิจารณาถึงเหตุผลในการใช้ Shadow DOM ใน Frankenstein Migration เราได้กล่าวถึงหัวข้อของการกำหนดขอบเขตและการห่อหุ้ม) และการห่อหุ้ม Shadow DOM แตกต่างจากเครื่องมือจัดรูปแบบการกำหนดขอบเขตอย่างไร อย่างไรก็ตาม เราไม่ได้อธิบายว่าทำไมเครื่องมือกำหนดขอบเขตจึงมีการจัดรูปแบบที่เปราะบางสำหรับส่วนประกอบของเรา และตอนนี้ เมื่อเราเผชิญกับองค์ประกอบ Alien ที่ไม่ได้จัดรูปแบบ มันกลายเป็นสิ่งจำเป็นสำหรับการทำความเข้าใจ

เครื่องมือกำหนดขอบเขตทั้งหมดสำหรับกรอบงานสมัยใหม่ทำงานในลักษณะเดียวกัน:

  • คุณเขียนสไตล์สำหรับองค์ประกอบของคุณในแบบใดแบบหนึ่งโดยไม่ต้องคิดมากเกี่ยวกับขอบเขตหรือการห่อหุ้ม
  • คุณเรียกใช้คอมโพเนนต์ของคุณด้วยสไตล์ชีตที่นำเข้า/ฝังผ่านระบบการรวมเข้าด้วยกัน เช่น Webpack หรือ Rollup
  • Bundler สร้างคลาส CSS เฉพาะหรือแอตทริบิวต์อื่นๆ สร้างและฉีดตัวเลือกแต่ละรายการสำหรับทั้ง HTML และสไตล์ชีตที่เกี่ยวข้อง
  • Bundler สร้างรายการ <style> ใน <head> ของเอกสารของคุณ และใส่สไตล์ของส่วนประกอบด้วยตัวเลือกที่ผสมผสานกันเฉพาะในนั้น

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

มาดูโฮสต์ปัจจุบันโดยใช้ DevTools ตัวอย่างเช่น เมื่อตรวจสอบเครื่องห่อ Frankenstein ที่เพิ่มใหม่ด้วยส่วนประกอบ Alien React เราจะเห็นสิ่งนี้:

กระดาษห่อแฟรงเกนสไตน์ที่มีส่วนประกอบเอเลี่ยนอยู่ภายใน สังเกตคลาส CSS ที่ไม่ซ้ำกันบนโหนดของ Alien
กระดาษห่อแฟรงเกนสไตน์ที่มีส่วนประกอบเอเลี่ยนอยู่ภายใน สังเกตคลาส CSS ที่ไม่ซ้ำกันบนโหนดของ Alien (ตัวอย่างขนาดใหญ่)

ดังนั้น Webpack จะสร้างคลาส CSS เฉพาะสำหรับส่วนประกอบของเรา ยอดเยี่ยม! แล้วสไตล์ไหนล่ะ? สไตล์ต่างๆ ก็ได้รับการออกแบบมาอย่างแม่นยำ — ใน <head> ของเอกสาร

แม้ว่าองค์ประกอบเอเลี่ยนจะอยู่ภายในกระดาษห่อหุ้มแฟรงเกนสไตน์ แต่รูปแบบขององค์ประกอบนั้นอยู่ในหัวของเอกสาร
แม้ว่าองค์ประกอบ Alien จะอยู่ภายใน wrapper ของ Frankenstein แต่สไตล์จะอยู่ใน <head> ของเอกสาร (ตัวอย่างขนาดใหญ่)

ดังนั้นทุกอย่างทำงานได้ตามที่ควรจะเป็น และนี่คือปัญหาหลัก เนื่องจากองค์ประกอบ Alien ของเราอยู่ใน Shadow DOM และตามที่อธิบายไว้ในส่วนที่ 1 Shadow DOM ให้การห่อหุ้มส่วนประกอบทั้งหมดจากส่วนที่เหลือของหน้าและรูปแบบสากล รวมถึงสไตล์ชีตที่สร้างขึ้นใหม่สำหรับส่วนประกอบที่ไม่สามารถข้ามขอบเงาและ ไปที่องค์ประกอบเอเลี่ยน ดังนั้นองค์ประกอบเอเลี่ยนจึงไม่มีสไตล์ อย่างไรก็ตาม ตอนนี้ กลวิธีในการแก้ปัญหาควรมีความชัดเจน: เราควรวางรูปแบบขององค์ประกอบใน Shadow DOM เดียวกันกับที่องค์ประกอบของเราอยู่ (แทนที่จะเป็น <head> ของเอกสาร)

5.4. แก้ไขรูปแบบสำหรับส่วนประกอบเอเลี่ยน

จนถึงขณะนี้ กระบวนการโยกย้ายไปยังเฟรมเวิร์กใดๆ ก็ยังเหมือนเดิม อย่างไรก็ตาม สิ่งต่าง ๆ เริ่มแตกต่างไปจากนี้: ทุกเฟรมเวิร์กมีคำแนะนำเกี่ยวกับวิธีการจัดรูปแบบส่วนประกอบ และด้วยเหตุนี้ วิธีจัดการกับปัญหาจึงแตกต่างกัน ในที่นี้ เราจะพูดถึงกรณีทั่วไปส่วนใหญ่ แต่ถ้าเฟรมเวิร์กที่คุณทำงานด้วยใช้วิธีการจัดสไตล์ที่ไม่เหมือนใคร คุณต้องคำนึงถึงกลยุทธ์พื้นฐาน เช่น การวางสไตล์ของคอมโพเนนต์ลงใน Shadow DOM แทน <head>

ในบทนี้ เราจะกล่าวถึงการแก้ไขสำหรับ:

  • สไตล์ที่มาพร้อมกับโมดูล CSS ใน Vue (กลยุทธ์สำหรับ CSS ที่มีขอบเขตเหมือนกัน);
  • สไตล์ที่รวมเข้ากับองค์ประกอบที่มีสไตล์ใน React;
  • โมดูล CSS ทั่วไปและสไตล์สากล ฉันรวมสิ่งเหล่านี้เข้าด้วยกันเพราะโดยทั่วไปแล้ว โมดูล CSS นั้นคล้ายกับสไตล์ชีตส่วนกลางมาก และสามารถนำเข้าได้โดยองค์ประกอบใดๆ ที่ทำให้สไตล์ถูกตัดการเชื่อมต่อจากส่วนประกอบเฉพาะใดๆ

ข้อจำกัดก่อน: ทุกสิ่งที่เราทำเพื่อแก้ไขการจัดสไตล์ไม่ควรทำให้องค์ประกอบของ Alien เสียหาย มิฉะนั้น เราจะสูญเสียความเป็นอิสระของระบบเอเลี่ยนและโฮสต์ของเรา ดังนั้น เพื่อแก้ไขปัญหาการจัดสไตล์ เราจะใช้การกำหนดค่าของผู้มัดรวมหรือเครื่องห่อของแฟรงเกนสไตน์

สไตล์ที่รวมอยู่ใน Vue และ Shadow DOM

หากคุณกำลังเขียนแอปพลิเคชัน Vue แสดงว่าคุณกำลังใช้ส่วนประกอบไฟล์เดียว หากคุณใช้ Webpack ด้วย คุณน่าจะคุ้นเคยกับตัวโหลดสองตัว vue-loader และ vue-style-loader แบบแรกอนุญาตให้คุณเขียนส่วนประกอบไฟล์เดียวในขณะที่ส่วนหลังฉีด CSS ของส่วนประกอบลงในเอกสารเป็นแท็ก <style> แบบไดนามิก โดยค่าเริ่มต้น vue-style-loader จะแทรกสไตล์ของส่วนประกอบลงใน <head> ของเอกสาร อย่างไรก็ตาม แพ็คเกจทั้งสองยอมรับตัวเลือก shadowMode ในการกำหนดค่า ซึ่งช่วยให้เราเปลี่ยนลักษณะการทำงานเริ่มต้นและรูปแบบการฉีด (ตามที่ชื่อตัวเลือกบอกเป็นนัย) ลงใน Shadow DOM ได้อย่างง่ายดาย มาดูการทำงานกัน

การกำหนดค่า Webpack

อย่างน้อยที่สุด ไฟล์คอนฟิกูเรชัน Webpack ควรประกอบด้วยสิ่งต่อไปนี้:

 const VueLoaderPlugin = require('vue-loader/lib/plugin'); ... module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { shadowMode: true } }, { test: /\.css$/, include: path.resolve(__dirname, '../vue'), use: [ { loader:'vue-style-loader', options: { shadowMode: true } }, 'css-loader' ] } ], plugins: [ new VueLoaderPlugin() ] }

ในแอปพลิเคชันจริง test: /\.css$/ block จะซับซ้อนมากขึ้น (อาจเกี่ยวข้องกับกฎ oneOf ) เพื่อพิจารณาการกำหนดค่าทั้ง Host และ Alien อย่างไรก็ตาม ในกรณีนี้ jQuery ของเรามีรูปแบบ <link rel="stylesheet"> ธรรมดาใน index.html ดังนั้นเราจึงไม่สร้างสไตล์สำหรับ Host ผ่าน Webpack และปลอดภัยที่จะรองรับ Alien เท่านั้น

การกำหนดค่า Wrapper

นอกจากการกำหนดค่า Webpack เรายังต้องอัปเดตเครื่องห่อ Frankenstein โดยชี้ Vue ไปยัง Shadow DOM ที่ถูกต้อง ใน Header-wrapper.js ของเรา การเรนเดอร์องค์ประกอบ Vue ควรรวมคุณสมบัติ shadowRoot ที่นำไปสู่ shadowRoot ของแรปเปอร์แฟรงเกนสไตน์:

 ... new Vue({ shadowRoot: this.shadowRoot, render: h => h(VueHeader) }).$mount(mountPoint); ...

หลังจากที่คุณอัปเดตไฟล์และรีสตาร์ทเซิร์ฟเวอร์ของคุณ คุณควรได้รับสิ่งนี้ใน DevTools ของคุณ:

สไตล์ที่รวมเข้ากับองค์ประกอบ Alien Vue ที่อยู่ภายใน wrapper Frankenstein พร้อมคลาส CSS ที่ไม่ซ้ำกันทั้งหมดที่เก็บรักษาไว้
สไตล์ที่รวมเข้ากับองค์ประกอบ Alien Vue ที่อยู่ภายใน wrapper Frankenstein พร้อมคลาส CSS ที่ไม่ซ้ำกันทั้งหมดที่เก็บรักษาไว้ (ตัวอย่างขนาดใหญ่)

สุดท้าย สไตล์สำหรับองค์ประกอบ Vue อยู่ใน Shadow DOM ของเรา ในขณะเดียวกัน ใบสมัครของคุณควรมีลักษณะดังนี้:

ส่วนประกอบส่วนหัวเริ่มมีลักษณะตามที่ควรจะเป็น อย่างไรก็ตาม ยังมีบางสิ่งที่ขาดหายไป
ส่วนประกอบส่วนหัวเริ่มมีลักษณะตามที่ควรจะเป็น อย่างไรก็ตาม ยังมีบางสิ่งที่ขาดหายไป (ตัวอย่างขนาดใหญ่)

เราเริ่มได้รับสิ่งที่คล้ายกับแอปพลิเคชัน Vue ของเรา: สไตล์ที่รวมเข้ากับส่วนประกอบ จะถูกฉีดเข้าไปใน Shadow DOM ของ wrapper แต่ส่วนประกอบยังคงดูไม่เป็นไปตามที่ควรจะเป็น เหตุผลก็คือในแอปพลิเคชัน Vue ดั้งเดิม ส่วนประกอบนั้นไม่เพียงแต่กำหนดสไตล์ด้วยสไตล์ที่รวมเข้าด้วยกันเท่านั้น แต่ยังรวมถึงสไตล์ส่วนกลางบางส่วนด้วย อย่างไรก็ตาม ก่อนที่จะแก้ไขสไตล์โกลบอล เราต้องทำให้การรวม React ของเราเป็นสถานะเดียวกับ Vue

สไตล์ที่รวมอยู่ใน React และ Shadow DOM

เนื่องจากมีหลายวิธีในการจัดรูปแบบองค์ประกอบ React โซลูชันเฉพาะในการแก้ไขส่วนประกอบ Alien ใน Frankenstein Migration จึงขึ้นอยู่กับวิธีที่เราจัดรูปแบบองค์ประกอบตั้งแต่แรก มาพูดถึงทางเลือกที่ใช้บ่อยที่สุดโดยสังเขปกัน

สไตล์องค์ประกอบ

styled-components เป็นหนึ่งในวิธีที่นิยมมากที่สุดในการใส่สไตล์ส่วนประกอบ React สำหรับองค์ประกอบ Header React ส่วนประกอบที่มีสไตล์คือรูปแบบที่เรากำหนดไว้อย่างแม่นยำ เนื่องจากเป็นแนวทาง CSS-in-JS แบบคลาสสิก จึงไม่มีไฟล์ที่มีนามสกุลเฉพาะที่เราสามารถเชื่อมต่อบันเดิลของเราเข้ากับไฟล์ .css หรือ . .js ได้ เป็นต้น โชคดีที่ styled-components อนุญาตให้ฉีดสไตล์ของส่วนประกอบลงในโหนดที่กำหนดเอง (Shadow DOM ในกรณีของเรา) แทนที่จะเป็นส่วน head ของเอกสารด้วยความช่วยเหลือขององค์ประกอบช่วยเหลือของ StyleSheetManager เป็นองค์ประกอบที่กำหนดไว้ล่วงหน้า ซึ่งติดตั้งพร้อมกับแพ็คเกจที่ styled-components ซึ่งยอมรับคุณสมบัติ target โดยกำหนด "โหนด DOM สำรองเพื่อฉีดข้อมูลรูปแบบ" ตรงที่เราต้องการ! ยิ่งกว่านั้น เราไม่จำเป็นต้องเปลี่ยนการกำหนดค่า Webpack ด้วยซ้ำ ทุกอย่างขึ้นอยู่กับเครื่องห่อ Frankenstein

เราควรอัปเดต Header-wrapper.js ที่มีส่วนประกอบ React Alien ด้วยบรรทัดต่อไปนี้:

 ... import { StyleSheetManager } from "../../react/node_modules/styled-components"; ... const target = this.shadowRoot; ReactDOM.render( <StyleSheetManager target={target}> <HeaderApp /> </StyleSheetManager>, appWrapper ); ...

ที่นี่ เรานำเข้าองค์ประกอบ StyleSheetManager (จาก Alien ไม่ใช่จาก Host) และรวมองค์ประกอบ React ไว้ด้วย ในเวลาเดียวกัน เราส่งคุณสมบัติ target ที่ชี้ไปที่ shadowRoot ของเรา แค่นั้นแหละ. หากคุณรีสตาร์ทเซิร์ฟเวอร์ คุณต้องเห็นสิ่งนี้ใน DevTools ของคุณ:

สไตล์ที่มาพร้อมกับองค์ประกอบ React Alien ที่อยู่ภายใน wrapper Frankenstein พร้อมคลาส CSS ที่ไม่ซ้ำกันทั้งหมดที่เก็บรักษาไว้
สไตล์ที่มาพร้อมกับองค์ประกอบ React Alien ที่อยู่ภายใน wrapper Frankenstein พร้อมคลาส CSS ที่ไม่ซ้ำกันทั้งหมดที่เก็บรักษาไว้ (ตัวอย่างขนาดใหญ่)

ตอนนี้ สไตล์คอมโพเนนต์ของเราอยู่ใน Shadow DOM แทนที่จะเป็น <head> ด้วยวิธีนี้ การเรนเดอร์แอปของเราตอนนี้จะคล้ายกับที่เราเคยเห็นในแอป Vue ก่อนหน้านี้

หลังจากย้ายสไตล์ที่รวมกลุ่มไว้ในเสื้อคลุม Frankenstein ส่วนประกอบ Alien React เริ่มดูดีขึ้น อย่างไรก็ตาม เรายังไม่ได้อยู่ที่นั่น
หลังจากย้ายสไตล์ที่รวมกลุ่มไว้ในเสื้อคลุม Frankenstein ส่วนประกอบ Alien React เริ่มดูดีขึ้น อย่างไรก็ตาม เรายังไม่ได้อยู่ที่นั่น (ตัวอย่างขนาดใหญ่)

เรื่องเดียวกัน: styled-components รับผิดชอบเฉพาะส่วนที่มัดรวมของ style ของส่วนประกอบ React และสไตล์โกลบอลจะจัดการบิตที่เหลือ เราจะกลับไปที่สไตล์สากลในอีกสักครู่หลังจากที่เราตรวจสอบส่วนประกอบการจัดสไตล์อีกประเภทหนึ่ง

โมดูล CSS

หากคุณพิจารณาองค์ประกอบ Vue ที่เราได้แก้ไขก่อนหน้านี้อย่างละเอียดถี่ถ้วน คุณอาจสังเกตเห็นว่า CSS Modules เป็นวิธีที่เราจัดรูปแบบองค์ประกอบนั้นอย่างแม่นยำ However, even if we style it with Scoped CSS (another recommended way of styling Vue components) the way we fix our unstyled component doesn't change: it is still up to vue-loader and vue-style-loader to handle it through shadowMode: true option.

When it comes to CSS Modules in React (or any other system using CSS Modules without any dedicated tools), things get a bit more complicated and less flexible, unfortunately.

Let's take a look at the same React component which we've just integrated, but this time styled with CSS Modules instead of styled-components. The main thing to note in this component is a separate import for stylesheet:

 import styles from './Header.module.css'

The .module.css extension is a standard way to tell React applications built with the create-react-app utility that the imported stylesheet is a CSS Module. The stylesheet itself is very basic and does precisely the same our styled-components do.

Integrating CSS modules into a Frankenstein wrapper consists of two parts:

  • Enabling CSS Modules in bundler,
  • Pushing resulting stylesheet into Shadow DOM.

I believe the first point is trivial: all you need to do is set { modules: true } for css-loader in your Webpack configuration. Since, in this particular case, we have a dedicated extension for our CSS Modules ( .module.css ), we can have a dedicated configuration block for it under the general .css configuration:

 { test: /\.css$/, oneOf: [ { test: /\.module\.css$/, use: [ ... { loader: 'css-loader', options: { modules: true, } } ] } ] }

Note : A modules option for css-loader is all we have to know about CSS Modules no matter whether it's React or any other system. When it comes to pushing resulting stylesheet into Shadow DOM, however, CSS Modules are no different from any other global stylesheet.

By now, we went through the ways of integrating bundled styles into Shadow DOM for the following conventional scenarios:

  • Vue components, styled with CSS Modules. Dealing with Scoped CSS in Vue components won't be any different;
  • React components, styled with styled-components;
  • Components styled with raw CSS Modules (without dedicated tools like those in Vue). For these, we have enabled support for CSS modules in Webpack configuration.

However, our components still don't look as they are supposed to because their styles partially come from global styles . Those global styles do not come to our Frankenstein wrappers automatically. Moreover, you might get into a situation in which your Alien components are styled exclusively with global styles without any bundled styles whatsoever. So let's finally fix this side of the story.

Global Styles And Shadow DOM

Having your components styled with global styles is neither wrong nor bad per se: every project has its requirements and limitations. However, the best you can do for your components if they rely on some global styles is to pull those styles into the component itself. This way, you have proper easy-to-maintain self-contained components with bundled styles.

Nevertheless, it's not always possible or reasonable to do so: several components might share some styling, or your whole styling architecture could be built using global stylesheets that are split into the modular structure, and so on.

So having an opportunity to pull in global styles into our Frankenstein wrappers wherever it's required is essential for the success of this type of migration. Before we get to an example, keep in mind that this part is the same for pretty much any framework of your choice — be it React, Vue or anything else using global stylesheets!

Let's get back to our Header component from the Vue application. Take a look at this import:

 import "todomvc-app-css/index.css";

This import is where we pull in the global stylesheet. In this case, we do it from the component itself. It's only one way of using global stylesheet to style your component, but it's not necessarily like this in your application.

Some parent module might add a global stylesheet like in our React application where we import index.css only in index.js , and then our components expect it to be available in the global scope. Your component's styling might even rely on a stylesheet, added with <style> or <link> to your index.html . มันไม่สำคัญ What matters, however, is that you should expect to either import global stylesheets in your Alien component (if it doesn't harm the Alien application) or explicitly in the Frankenstein wrapper. Otherwise, the wrapper would not know that the Alien component needs any stylesheet other than the ones already bundled with it.

Caution . If there are many global stylesheets to be shared between Alien components and you have a lot of such components, this might harm the performance of your Host application under the migration period.

Here is how import of a global stylesheet, required for the Header component, is done in Frankenstein wrapper for React component:

 // we import directly from react/, not from Host import '../../react/node_modules/todomvc-app-css/index.css'

Nevertheless, by importing a stylesheet this way, we still bring the styles to the global scope of our Host, while what we need is to pull in the styles into our Shadow DOM. เราจะทำเช่นนี้ได้อย่างไร?

Webpack configuration for global stylesheets & Shadow DOM

First of all, you might want to add an explicit test to make sure that we process only the stylesheets coming from our Alien. In case of our React migration, it will look similar to this:

 test: /\.css$/, oneOf: [ // this matches stylesheets coming from /react/ subfolder { test: /\/react\//, use: [] }, ... ]

In case of Vue application, obviously, you change test: /\/react\// with something like test: /\/vue\// . Apart from that, the configuration will be the same for any framework. Next, let's specify the required loaders for this block.

 ... use: [ { loader: 'style-loader', options: { ... } }, 'css-loader' ]

Two things to note. First, you have to specify modules: true in css-loader 's configuration if you're processing CSS Modules of your Alien application.

Second, we should convert styles into <style> tag before injecting those into Shadow DOM. In the case of Webpack, for that, we use style-loader . The default behavior for this loader is to insert styles into the document's head. Typically. And this is precisely what we don't want: our goal is to get stylesheets into Shadow DOM. However, in the same way we used target property for styled-components in React or shadowMode option for Vue components that allowed us to specify custom insertion point for our <style> tags, regular style-loader provides us with nearly same functionality for any stylesheet: the insert configuration option is exactly what helps us achieve our primary goal. ข่าวดี! Let's add it to our configuration.

 ... { loader: 'style-loader', options: { insert: 'frankenstein-header-wrapper' } }

However, not everything is so smooth here with a couple of things to keep in mind.

สไตล์ชีตสากลและตัวเลือกการ insert ของ style-loader

หากคุณตรวจสอบเอกสารประกอบสำหรับตัวเลือกนี้ คุณจะสังเกตเห็นว่าตัวเลือกนี้ใช้ตัวเลือกหนึ่งตัวเลือกต่อการกำหนดค่า ซึ่งหมายความว่าหากคุณมีส่วนประกอบต่าง ๆ ของ Alien ที่ต้องใช้รูปแบบสากลดึงเข้าไปในเครื่องห่อ Frankenstein คุณต้องระบุ style-loader สำหรับเครื่องห่อ Frankenstein แต่ละเครื่อง ในทางปฏิบัติ นี่หมายความว่าคุณอาจต้องพึ่งพากฎ oneOf ในบล็อกการกำหนดค่าของคุณเพื่อให้บริการกับ wrapper ทั้งหมด

 { test: /\/react\//, oneOf: [ { test: /1-TEST-FOR-ALIEN-FILE-PATH$/, use: [ { loader: 'style-loader', options: { insert: '1-frankenstein-wrapper' } }, `css-loader` ] }, { test: /2-TEST-FOR-ALIEN-FILE-PATH$/, use: [ { loader: 'style-loader', options: { insert: '2-frankenstein-wrapper' } }, `css-loader` ] }, // etc. ], }

ไม่ค่อยคล่องเท่าไหร่ เห็นด้วย อย่างไรก็ตาม ไม่ใช่เรื่องใหญ่ตราบใดที่คุณไม่มีส่วนประกอบหลายร้อยรายการที่จะย้าย มิฉะนั้น อาจทำให้การกำหนดค่า Webpack ของคุณทำได้ยาก อย่างไรก็ตาม ปัญหาที่แท้จริงคือเราไม่สามารถเขียนตัวเลือก CSS สำหรับ Shadow DOM ได้

ในการพยายามแก้ปัญหานี้ เราอาจสังเกตว่าตัวเลือกการ insert สามารถใช้ฟังก์ชันแทนตัวเลือกธรรมดาเพื่อระบุตรรกะขั้นสูงสำหรับการแทรกได้ ด้วยเหตุนี้ เราจึงสามารถใช้ตัวเลือกนี้เพื่อแทรกสไตล์ชีตลงใน Shadow DOM ได้โดยตรง! ในรูปแบบที่เรียบง่าย อาจมีลักษณะดังนี้:

 insert: function(element) { var parent = document.querySelector('frankenstein-header-wrapper').shadowRoot; parent.insertBefore(element, parent.firstChild); }

ล่อใจใช่มั้ย? อย่างไรก็ตาม วิธีนี้ใช้ไม่ได้กับสถานการณ์ของเราหรือทำงานได้ไม่มีประสิทธิภาพสูงสุด <frankenstein-header-wrapper> ของเรามีอยู่ใน index.html (เพราะเราเพิ่มมันในขั้นตอนที่ 5.2) แต่เมื่อ Webpack ประมวลผลการพึ่งพาทั้งหมด (รวมถึงสไตล์ชีต) สำหรับส่วนประกอบ Alien หรือ Wrapper ของ Frankenstein Shadow DOM ยังไม่ได้เริ่มต้นใน wrapper Frankenstein: การนำเข้าจะได้รับการประมวลผลก่อนหน้านั้น ดังนั้น การชี้การ insert ตรงไปที่ shadowRoot จะทำให้เกิดข้อผิดพลาด

มีเพียงกรณีเดียวเท่านั้นที่เรารับประกันได้ว่า Shadow DOM จะเริ่มต้นก่อนที่ Webpack จะประมวลผลการพึ่งพาสไตล์ชีตของเรา หากองค์ประกอบ Alien ไม่ได้นำเข้าสไตล์ชีตเอง และมันก็ขึ้นอยู่กับตัวห่อหุ้ม Frankenstein เพื่อนำเข้า เราอาจใช้การนำเข้าแบบไดนามิกและนำเข้าสไตล์ชีตที่จำเป็นหลังจากที่เราตั้งค่า Shadow DOM:

 this.attachShadow({ mode: "open" }); import('../vue/node_modules/todomvc-app-css/index.css');

สิ่งนี้จะได้ผล: การนำเข้าดังกล่าว เมื่อรวมกับการกำหนดค่าการ insert ด้านบน จะพบ Shadow DOM ที่ถูกต้องและแทรกแท็ก <style> เข้าไป อย่างไรก็ตาม การรับและประมวลผลสไตล์ชีตจะใช้เวลา ซึ่งหมายความว่าผู้ใช้ของคุณที่มีการเชื่อมต่อช้าหรืออุปกรณ์ที่ช้าอาจเผชิญกับองค์ประกอบที่ไม่มีสไตล์สักครู่ก่อนที่สไตล์ชีตของคุณจะเข้ามาแทนที่ใน Shadow DOM ของ wrapper

คอมโพเนนต์ Unstyled Alien จะแสดงผลก่อนที่จะนำเข้าและเพิ่มสไตล์ชีตส่วนกลางไปยัง Shadow DOM
คอมโพเนนต์ Unstyled Alien จะแสดงผลก่อนที่จะนำเข้าและเพิ่มสไตล์ชีตส่วนกลางไปยัง Shadow DOM (ตัวอย่างขนาดใหญ่)

โดยรวมแล้ว แม้ว่าการ insert ยอมรับฟังก์ชัน แต่น่าเสียดายที่มันยังไม่เพียงพอสำหรับเรา และเราต้องถอยกลับไปใช้ตัวเลือก CSS ธรรมดาเช่น frankenstein-header-wrapper สิ่งนี้จะไม่วางสไตล์ชีตลงใน Shadow DOM โดยอัตโนมัติ และสไตล์ชีตจะอยู่ใน <frankenstein-header-wrapper> ภายนอก Shadow DOM

style-loader นำสไตล์ชีตที่นำเข้ามาไว้ใน wrapper ของ Frankenstein แต่อยู่นอก Shadow DOM
style-loader นำสไตล์ชีตที่นำเข้ามาไว้ใน wrapper ของ Frankenstein แต่อยู่นอก Shadow DOM (ตัวอย่างขนาดใหญ่)

เราต้องการปริศนาอีกชิ้นหนึ่ง

การกำหนดค่า Wrapper สำหรับสไตล์ชีตส่วนกลาง & Shadow DOM

โชคดีที่การแก้ไขนั้นค่อนข้างตรงไปตรงมาในฝั่งของ wrapper เมื่อ Shadow DOM ได้รับการเตรียมใช้งาน เราจำเป็นต้องตรวจสอบสไตล์ชีตที่รอดำเนินการใดๆ ใน wrapper ปัจจุบัน และดึงเข้าไปใน Shadow DOM

สถานะปัจจุบันของการนำเข้าของสไตล์ชีตส่วนกลางมีดังนี้:

  • เรานำเข้าสไตล์ชีตที่ต้องเพิ่มลงใน Shadow DOM สไตล์ชีตสามารถนำเข้าได้ทั้งในองค์ประกอบ Alien เอง หรือใน Wrapper ของ Frankenstein อย่างชัดเจน ในกรณีของการโยกย้ายไปยัง React ตัวอย่างเช่น การอิมพอร์ตเริ่มต้นจาก wrapper อย่างไรก็ตาม ในการโยกย้ายไปยัง Vue ส่วนประกอบที่คล้ายกันจะนำเข้าสไตล์ชีตที่จำเป็น และเราไม่ต้องนำเข้าสิ่งใดในเสื้อคลุม
  • ดังที่ได้กล่าวไว้ข้างต้น เมื่อ Webpack ประมวลผลการนำเข้า .css สำหรับองค์ประกอบ Alien ด้วยตัวเลือกการ insert ของ style-loader สไตล์ชีตจะถูกฉีดเข้าไปในเครื่องห่อหุ้ม Frankenstein แต่อยู่นอก Shadow DOM

การเริ่มต้นอย่างง่ายของ Shadow DOM ในเครื่องห่อหุ้ม Frankenstein ปัจจุบันควร (ก่อนที่เราจะดึงสไตล์ชีตใด ๆ ) มีลักษณะดังนี้:

 this.attachShadow({ mode: "open" }); ReactDOM.render(); // or `new Vue()`

ตอนนี้ เพื่อหลีกเลี่ยงไม่ให้องค์ประกอบที่ไม่ได้จัดรูปแบบสั่นไหว สิ่งที่เราต้องทำตอนนี้คือการดึงสไตล์ชีตที่จำเป็นทั้งหมด หลังจาก เริ่มต้น Shadow DOM แต่ ก่อน การเรนเดอร์องค์ประกอบเอเลี่ยน

 this.attachShadow({ mode: "open" }); Array.prototype.slice .call(this.querySelectorAll("style")) .forEach(style => { this.shadowRoot.prepend(style); }); ReactDOM.render(); // or new Vue({})

เป็นคำอธิบายที่ยาวและมีรายละเอียดมากมาย แต่หลักๆ แล้ว ทั้งหมดที่ใช้ในการดึงสไตล์ชีตส่วนกลางเข้าสู่ Shadow DOM:

  • ในการกำหนดค่า Webpack ให้เพิ่ม style-loader พร้อมตัวเลือกการ insert ที่ชี้ไปยังเครื่องห่อ Frankenstein ที่จำเป็น
  • ใน wrapper เอง ให้ดึงสไตล์ชีตที่ "รอดำเนินการ" หลังจากเริ่มต้น Shadow DOM แต่ก่อนการเรนเดอร์องค์ประกอบ Alien

หลังจากใช้การเปลี่ยนแปลงเหล่านี้ คอมโพเนนต์ของคุณควรมีทุกสิ่งที่จำเป็น สิ่งเดียวที่คุณอาจต้องการ (นี่ไม่ใช่ข้อกำหนด) เพื่อเพิ่มคือ CSS ที่กำหนดเองเพื่อปรับแต่งองค์ประกอบ Alien ในสภาพแวดล้อมของ Host คุณอาจจัดรูปแบบองค์ประกอบ Alien ของคุณให้แตกต่างไปจากเดิมอย่างสิ้นเชิงเมื่อใช้ใน Host มันไปไกลกว่าประเด็นหลักของบทความ แต่คุณดูที่โค้ดสุดท้ายของ wrapper ซึ่งคุณจะพบตัวอย่างวิธีการแทนที่สไตล์อย่างง่ายในระดับ wrapper

  • กระดาษห่อหุ้ม Frankenstein สำหรับส่วนประกอบ React
  • กระดาษห่อหุ้มแฟรงเกนสไตน์สำหรับส่วนประกอบ Vue

คุณยังสามารถดูการกำหนดค่า Webpack ได้ในขั้นตอนนี้ของการย้ายข้อมูล:

  • การโยกย้ายเพื่อทำปฏิกิริยากับองค์ประกอบที่มีสไตล์
  • การโยกย้ายเพื่อตอบสนองต่อโมดูล CSS
  • การโยกย้ายไปยัง Vue

และสุดท้าย ส่วนประกอบของเราจะมีลักษณะตรงตามที่เราตั้งใจไว้

ผลลัพธ์ของการย้ายองค์ประกอบส่วนหัวที่เขียนด้วย Vue และ React รายการสิ่งที่ต้องทำยังคงเป็นแอปพลิเคชัน jQuery
ผลลัพธ์ของการย้ายองค์ประกอบส่วนหัวที่เขียนด้วย Vue และ React รายการสิ่งที่ต้องทำยังคงเป็นแอปพลิเคชัน jQuery (ตัวอย่างขนาดใหญ่)

5.5. สรุปรูปแบบการตรึงสำหรับส่วนประกอบเอเลี่ยน

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

  • การแก้ไขสไตล์ที่รวมกลุ่มมาใช้กับองค์ประกอบที่มีสไตล์ในโมดูล React หรือ CSS และ Scoped CSS ใน Vue นั้นง่ายพอๆ กับสองสามบรรทัดใน Frankenstein wrapper หรือการกำหนดค่า Webpack
  • การแก้ไขรูปแบบที่นำไปใช้กับโมดูล CSS เริ่มต้นด้วยเพียงหนึ่งบรรทัดในการกำหนดค่า css-loader หลังจากนั้น โมดูล CSS จะถือเป็นสไตล์ชีตส่วนกลาง
  • การแก้ไข global stylesheets ต้องการการกำหนดค่าแพ็คเกจ style-loader ด้วยตัวเลือก insert ใน Webpack และการอัปเดต wrapper Frankenstein เพื่อดึงสไตล์ชีตเข้าสู่ Shadow DOM ในช่วงเวลาที่เหมาะสมของวงจรชีวิตของ wrapper

ท้ายที่สุด เราได้ย้ายองค์ประกอบเอเลี่ยนที่มีสไตล์อย่างเหมาะสมไปยังโฮสต์แล้ว อย่างไรก็ตาม มีสิ่งหนึ่งที่อาจหรือไม่อาจรบกวนคุณขึ้นอยู่กับว่ากรอบงานใดที่คุณย้ายไป

ข่าวดีก่อน: หากคุณกำลังย้ายไปยัง Vue การสาธิตควรทำงานได้ดี และคุณควรจะเพิ่มรายการสิ่งที่ต้องทำใหม่จากคอมโพเนนต์ Vue ที่ย้ายข้อมูลได้ อย่างไรก็ตาม หากคุณกำลังย้ายไปยัง React และพยายามเพิ่มรายการสิ่งที่ต้องทำใหม่ คุณจะไม่ประสบความสำเร็จ การเพิ่มรายการใหม่ใช้งานไม่ได้ และไม่มีการเพิ่มรายการลงในรายการ แต่ทำไม? มีปัญหาอะไร? ไม่มีอคติ แต่ React มีความคิดเห็นของตัวเองในบางเรื่อง

5.6. ปฏิกิริยาและเหตุการณ์ JS ใน Shadow DOM

ไม่ว่าเอกสาร React จะบอกคุณว่าอย่างไร React ก็ไม่เป็นมิตรกับ Web Components มากนัก ความเรียบง่ายของตัวอย่างในเอกสารประกอบไม่สามารถทนต่อการวิพากษ์วิจารณ์ใดๆ และอะไรก็ตามที่ซับซ้อนกว่าการแสดงลิงก์ใน Web Component จำเป็นต้องมีการวิจัยและการตรวจสอบ

อย่างที่คุณเห็นในขณะที่กำลังแก้ไขสไตล์สำหรับองค์ประกอบ Alien ของเรา ซึ่งตรงกันข้ามกับ Vue ที่ซึ่งสิ่งต่างๆ พอดีกับ Web Components เกือบจะพร้อมใช้งานแล้ว React ไม่ใช่ Web Components ที่พร้อม สำหรับตอนนี้ เรามีความเข้าใจเกี่ยวกับวิธีการทำให้ส่วนประกอบ React ดูดีภายใน Web Components อย่างน้อย แต่ก็ยังมีฟังก์ชันและเหตุการณ์ JavaScript ที่ต้องแก้ไข

เรื่องสั้นแบบยาว: Shadow DOM สรุปเหตุการณ์และกำหนดเป้าหมายใหม่ ขณะที่ React ไม่รองรับพฤติกรรมนี้ของ Shadow DOM ดั้งเดิม ดังนั้นจึงไม่ตรวจจับเหตุการณ์ที่มาจากภายใน Shadow DOM มีเหตุผลที่ลึกซึ้งกว่าสำหรับพฤติกรรมนี้ และยังมีปัญหาที่เปิดอยู่ในตัวติดตามจุดบกพร่องของ React หากคุณต้องการเจาะลึกรายละเอียดและการอภิปรายเพิ่มเติม

โชคดีที่คนฉลาดเตรียมวิธีแก้ปัญหาให้เรา @josephnvu เป็นพื้นฐานสำหรับการแก้ปัญหาและ Lukas Bombach แปลงเป็นโมดูล react-shadow-dom-retarget-events npm ดังนั้น คุณสามารถติดตั้งแพ็คเกจ ทำตามคำแนะนำบนหน้าแพ็คเกจ อัปเดตรหัสของ wrapper และส่วนประกอบ Alien ของคุณจะเริ่มทำงานอย่างน่าอัศจรรย์:

 import retargetEvents from 'react-shadow-dom-retarget-events'; ... ReactDOM.render( ... ); retargetEvents(this.shadowRoot);

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

ด้วยสิ่งนี้ ในที่สุดเราก็ (ฉันรู้ว่ามันเป็นกระบวนการที่ยาวนาน) เสร็จสิ้นด้วยการโยกย้ายที่เหมาะสมขององค์ประกอบเอเลี่ยนตัวแรกที่มีสไตล์และทำงานได้อย่างสมบูรณ์ หาเครื่องดื่มดีๆ ให้ตัวเอง คุณสมควรที่จะได้รับมัน!

6. ล้างและทำซ้ำสำหรับส่วนประกอบทั้งหมดของคุณ

หลังจากที่เราโอนย้ายองค์ประกอบแรกแล้ว เราควรทำซ้ำขั้นตอนสำหรับส่วนประกอบทั้งหมดของเรา ในกรณีของ Frankenstein Demo เหลือเพียงอันเดียวเท่านั้น: อันเดียวที่รับผิดชอบในการแสดงรายการสิ่งที่ต้องทำ

Wrappers ใหม่สำหรับส่วนประกอบใหม่

เริ่มต้นด้วยการเพิ่มเสื้อคลุมใหม่ ตามแบบแผนการตั้งชื่อที่กล่าวถึงข้างต้น (เนื่องจากองค์ประกอบ React ของเราเรียกว่า MainSection.js ) wrapper ที่เกี่ยวข้องในการโยกย้ายไปยัง React ควรถูกเรียกว่า MainSection-wrapper.js ในเวลาเดียวกัน องค์ประกอบที่คล้ายกันใน Vue เรียกว่า Listing.vue ดังนั้น wrapper ที่เกี่ยวข้องในการโยกย้ายไปยัง Vue ควรเรียกว่า Listing-wrapper.js อย่างไรก็ตาม ไม่ว่ารูปแบบการตั้งชื่อจะเป็นแบบใด ตัวห่อหุ้มเองก็เกือบจะเหมือนกับที่เรามีอยู่แล้ว:

  • Wrapper สำหรับรายการ React
  • Wrapper สำหรับรายการ Vue

มีเพียงสิ่งที่น่าสนใจที่เราแนะนำในองค์ประกอบที่สองนี้ในแอปพลิเคชัน React ในบางครั้ง คุณอาจต้องการใช้ปลั๊กอิน jQuery ในส่วนประกอบของคุณด้วยเหตุผลดังกล่าวหรือเหตุผลอื่น ในกรณีของส่วนประกอบ React เราแนะนำสองสิ่ง:

  • ปลั๊กอินคำแนะนำเครื่องมือจาก Bootstrap ที่ใช้ jQuery
  • สลับสำหรับคลาส CSS เช่น .addClass() และ .removeClass()

    หมายเหตุ : การใช้ jQuery สำหรับการเพิ่ม/ลบคลาสนี้เป็นเพียงตัวอย่างเท่านั้น โปรดอย่าใช้ jQuery สำหรับสถานการณ์นี้ในโปรเจ็กต์จริง — ให้พึ่งพา JavaScript ธรรมดาแทน

แน่นอน การแนะนำ jQuery ในองค์ประกอบ Alien อาจดูแปลกเมื่อเราย้ายออกจาก jQuery แต่โฮสต์ของคุณอาจแตกต่างจากโฮสต์ในตัวอย่างนี้ คุณอาจย้ายออกจาก AngularJS หรืออย่างอื่น นอกจากนี้ ฟังก์ชัน jQuery ในส่วนประกอบและ jQuery ส่วนกลางไม่จำเป็นต้องเป็นสิ่งเดียวกัน

อย่างไรก็ตาม ปัญหาคือแม้ว่าคุณจะยืนยันว่าส่วนประกอบนั้นทำงานได้ดีในบริบทของแอปพลิเคชัน Alien ของคุณ เมื่อคุณใส่ลงใน Shadow DOM ปลั๊กอิน jQuery ของคุณและโค้ดอื่นๆ ที่ใช้ jQuery จะไม่ทำงาน

jQuery ในเงา DOM

มาดูการเริ่มต้นทั่วไปของปลั๊กอิน jQuery แบบสุ่ม:

 $('.my-selector').fancyPlugin();

ด้วยวิธีนี้ องค์ประกอบทั้งหมดที่มี . .my-selector จะถูกประมวลผลโดย fancyPlugin รูปแบบการเริ่มต้นนี้ถือว่า . .my-selector มีอยู่ใน DOM ส่วนกลาง อย่างไรก็ตาม เมื่อองค์ประกอบดังกล่าวถูกใส่ลงใน Shadow DOM เช่นเดียวกับสไตล์ ขอบเขตของเงาจะป้องกันไม่ให้ jQuery แอบเข้าไป เป็นผลให้ jQuery ไม่พบองค์ประกอบภายใน Shadow DOM

วิธีแก้ไขคือจัดเตรียมพารามิเตอร์ที่สองที่เป็นทางเลือกให้กับตัวเลือกที่กำหนดองค์ประกอบรูทสำหรับ jQuery เพื่อค้นหา และนี่คือที่ที่เราสามารถจัดหา shadowRoot ของเราได้

 $('.my-selector', this.shadowRoot).fancyPlugin();

ด้วยวิธีนี้ ตัวเลือก jQuery และด้วยเหตุนี้ ปลั๊กอินจึงทำงานได้ดี

พึงระลึกไว้เสมอว่าส่วนประกอบ Alien นั้นมีวัตถุประสงค์เพื่อใช้ทั้งใน Alien ที่ไม่มี Shadow DOM และใน Host ภายใน Shadow DOM ดังนั้นเราจึงต้องการโซลูชันที่เป็นหนึ่งเดียวกันมากขึ้นซึ่งจะไม่ถือว่ามี Shadow DOM เป็นค่าเริ่มต้น

การวิเคราะห์องค์ประกอบ MainSection ในแอปพลิเคชัน React ของเรา เราพบว่ามันตั้งค่าคุณสมบัติ documentRoot

 ... this.documentRoot = this.props.root? this.props.root: document; ...

ดังนั้นเราจึงตรวจสอบคุณสมบัติ root ทที่ส่งผ่าน และหากมีอยู่ นี่คือสิ่งที่เราใช้เป็น documentRoot มิฉะนั้น เราถอยกลับไป document

นี่คือการเริ่มต้นของปลั๊กอินคำแนะนำเครื่องมือที่ใช้คุณสมบัตินี้:

 $('[data-toggle="tooltip"]', this.documentRoot).tooltip({ container: this.props.root || 'body' });

เป็นโบนัส เราใช้คุณสมบัติ root ทเดียวกันเพื่อกำหนดคอนเทนเนอร์สำหรับการฉีดคำแนะนำเครื่องมือในกรณีนี้

ตอนนี้ เมื่อองค์ประกอบ Alien พร้อมที่จะยอมรับคุณสมบัติ root ท เราจะอัปเดตการเรนเดอร์องค์ประกอบในแรปเปอร์ Frankenstein ที่สอดคล้องกัน:

 // `appWrapper` is the root element within wrapper's Shadow DOM. ReactDOM.render(<MainApp root={ appWrapper } />, appWrapper);

และนั่นแหล่ะ! องค์ประกอบทำงานได้ดีใน Shadow DOM เช่นเดียวกับใน DOM ส่วนกลาง

การกำหนดค่า Webpack สำหรับสถานการณ์จำลอง multi-wrappers

ส่วนที่น่าตื่นเต้นกำลังเกิดขึ้นในการกำหนดค่าของ Webpack เมื่อใช้เครื่องห่อหลายตัว ไม่มีอะไรเปลี่ยนแปลงสำหรับสไตล์ที่รวมกลุ่มเช่นโมดูล CSS เหล่านั้นในองค์ประกอบ Vue หรือองค์ประกอบที่มีสไตล์ใน React อย่างไรก็ตาม สไตล์สากลน่าจะเปลี่ยนไปเล็กน้อยในตอนนี้

จำไว้ว่า เรากล่าวว่า style-loader (รับผิดชอบในการฉีด global stylesheets ลงใน Shadow DOM ที่ถูกต้อง) นั้นไม่ยืดหยุ่น เนื่องจากใช้ตัวเลือกทีละตัวสำหรับตัวเลือกการ insert ซึ่งหมายความว่าเราควรแยกกฎ .css ใน Webpack เพื่อให้มีกฎย่อยหนึ่งกฎต่อ wrapper โดยใช้กฎ oneOf หรือคล้ายกัน หากคุณใช้ Bundler อื่นที่ไม่ใช่ Webpack

อธิบายได้ง่ายกว่าเสมอโดยใช้ตัวอย่าง ดังนั้นเรามาพูดถึงการโยกย้ายจากการย้ายไปยัง Vue ในครั้งนี้ (อย่างไรก็ตาม การโยกย้ายไปยัง React เกือบจะเหมือนกัน):

 ... oneOf: [ { issuer: /Header/, use: [ { loader: 'style-loader', options: { insert: 'frankenstein-header-wrapper' } }, ... ] }, { issuer: /Listing/, use: [ { loader: 'style-loader', options: { insert: 'frankenstein-listing-wrapper' } }, ... ] }, ] ...

ฉันได้ยกเว้น css-loader เนื่องจากการกำหนดค่าจะเหมือนกันในทุกกรณี มาพูดถึง style-loader กันดีกว่า ในการกำหนดค่านี้ เราแทรกแท็ก <style> ลงใน *-header-* หรือ *-listing-* ขึ้นอยู่กับชื่อของไฟล์ที่ร้องขอสไตล์ชีตนั้น (กฎของ issuer ใน Webpack) แต่เราต้องจำไว้ว่า global stylesheet ที่จำเป็นสำหรับการแสดงองค์ประกอบ Alien อาจถูกนำเข้าในสองแห่ง:

  • องค์ประกอบเอเลี่ยนนั้นเอง
  • กระดาษห่อแฟรงเกนสไตน์

และที่นี่ เราควรซาบซึ้งกับการตั้งชื่อแบบแผนสำหรับ wrapper ตามที่อธิบายข้างต้น เมื่อชื่อขององค์ประกอบ Alien และ wrapper ที่เกี่ยวข้องกัน ตัวอย่างเช่น หากเรามีสไตล์ชีตซึ่งนำเข้าในองค์ประกอบ Vue ชื่อ Header.vue ก็จะได้รับการแก้ไข *-header-* wrapper ในเวลาเดียวกัน หากเรานำเข้าสไตล์ชีตใน wrapper แทน สไตล์ชีตดังกล่าวจะปฏิบัติตามกฎเดียวกันอย่างแม่นยำ หาก wrapper ถูกเรียกว่า Header-wrapper.js โดยไม่มีการเปลี่ยนแปลงใดๆ ในการกำหนดค่า สิ่งเดียวกันสำหรับองค์ประกอบ Listing.vue และ wrapper Listing-wrapper.js เกี่ยวข้อง โดยใช้หลักการตั้งชื่อนี้ เราลดการกำหนดค่าในชุดรวมของเรา

หลังจากย้ายส่วนประกอบทั้งหมดแล้ว ก็ถึงเวลาสำหรับขั้นตอนสุดท้ายของการย้ายข้อมูล

7. เปลี่ยนไปใช้เอเลี่ยน

เมื่อถึงจุดหนึ่ง คุณจะพบว่าส่วนประกอบที่คุณระบุในขั้นตอนแรกของการย้ายข้อมูล ทั้งหมดจะถูกแทนที่ด้วยเครื่องห่อ Frankenstein ไม่มีแอปพลิเคชั่น jQuery เหลืออยู่จริง ๆ และสิ่งที่คุณมีก็คือแอปพลิเคชั่น Alien ที่ติดกาวเข้าด้วยกันโดยใช้วิธีการของโฮสต์

ตัวอย่างเช่น ส่วนเนื้อหาของ index.html ในแอปพลิเคชัน jQuery หลังจากการโยกย้ายไมโครเซอร์วิสทั้งสอง จะมีลักษณะดังนี้:

 <section class="todoapp"> <frankenstein-header-wrapper></frankenstein-header-wrapper> <frankenstein-listing-wrapper></frankenstein-listing-wrapper> </section>

ในขณะนี้ มันไม่มีประโยชน์อะไรที่จะรักษาแอปพลิเคชัน jQuery ไว้ได้: เราควรเปลี่ยนไปใช้แอปพลิเคชัน Vue และลืมเกี่ยวกับ wrapper, Shadow DOM และการกำหนดค่า Webpack แฟนซีทั้งหมดของเรา ในการทำเช่นนี้ เรามีโซลูชันที่หรูหรา

มาพูดถึงคำขอ HTTP ฉันจะพูดถึงการกำหนดค่า Apache ที่นี่ แต่นี่เป็นเพียงรายละเอียดการใช้งาน: การทำสวิตช์ใน Nginx หรืออย่างอื่นควรไม่สำคัญเท่าใน Apache

ลองนึกภาพว่าคุณมีไซต์ของคุณให้บริการจากโฟลเดอร์ /var/www/html บนเซิร์ฟเวอร์ของคุณ ในกรณีนี้ httpd.conf หรือ httpd-vhost.conf ควรมีรายการที่ชี้ไปยังโฟลเดอร์นั้น เช่น:

 DocumentRoot "/var/www/html"

หากต้องการเปลี่ยนแอปพลิเคชันของคุณหลังจากการโยกย้าย Frankenstein จาก jQuery เป็น React สิ่งที่คุณต้องทำคืออัปเดตรายการ DocumentRoot เป็นดังนี้:

 DocumentRoot "/var/www/html/react/build"

สร้างแอปพลิเคชัน Alien ของคุณ รีสตาร์ทเซิร์ฟเวอร์ของคุณ และแอปพลิเคชันของคุณได้รับบริการโดยตรงจากโฟลเดอร์ของ Alien: แอปพลิเคชัน React ที่ให้บริการจากโฟลเดอร์ react/ อย่างไรก็ตาม เช่นเดียวกับ Vue หรือเฟรมเวิร์กอื่นๆ ที่คุณได้ย้ายมาก็เช่นเดียวกัน นี่คือเหตุผลสำคัญอย่างยิ่งที่จะต้องทำให้ Host และ Alien เป็นอิสระและทำงานได้ตลอดเวลา เพราะ Alien ของคุณจะกลายเป็น Host ของคุณในขั้นตอนนี้

ตอนนี้คุณสามารถลบทุกอย่างในโฟลเดอร์ของ Alien ได้อย่างปลอดภัย ซึ่งรวมถึง Shadow DOM, เครื่องห่อ Frankenstein และสิ่งประดิษฐ์อื่น ๆ ที่เกี่ยวข้องกับการย้ายข้อมูล มันเป็นเส้นทางที่ยากลำบากในบางครั้ง แต่คุณได้ย้ายไซต์ของคุณแล้ว ยินดีด้วย!

บทสรุป

เราได้ผ่านภูมิประเทศที่ค่อนข้างขรุขระในบทความนี้อย่างแน่นอน อย่างไรก็ตาม หลังจากที่เราเริ่มต้นด้วยแอปพลิเคชัน jQuery เราก็สามารถย้ายไปยังทั้ง Vue และ React ได้ เราได้ค้นพบปัญหาที่ไม่คาดคิดและไม่สำคัญระหว่างทาง: เราต้องแก้ไขสไตล์ เราต้องแก้ไขฟังก์ชันการทำงานของ JavaScript แนะนำการกำหนดค่าบันเดิล และอื่นๆ อีกมากมาย อย่างไรก็ตาม มันทำให้เราเห็นภาพรวมที่ดีขึ้นของสิ่งที่คาดหวังในโครงการจริง ในท้ายที่สุด เรามีแอปพลิเคชันร่วมสมัยที่ไม่มีบิตเหลือจากแอปพลิเคชัน jQuery แม้ว่าเรามีสิทธิ์ทั้งหมดที่จะสงสัยเกี่ยวกับผลลัพธ์สุดท้ายในขณะที่กำลังดำเนินการย้ายข้อมูล

หลังจากเปลี่ยนไปใช้ Alien แล้ว Frankenstein สามารถเกษียณได้
หลังจากเปลี่ยนไปใช้ Alien แล้ว Frankenstein สามารถเกษียณได้ (ตัวอย่างขนาดใหญ่)

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