มาสร้างแอปที่มีส่วนหน้าแบบไมโครด้วยกัน

ในบทความนี้ ผมจะอธิบายวิธีใช้งานแอปส่วนหน้าแบบไมโครด้วย single-spa และ module federation ใน Webpack

ตัวอย่างพื้นที่เก็บข้อมูล

นี่คือโค้ดเบสสุดท้ายบน GitHub:



สารบัญ

ไมโครฟรอนต์เอนด์

Micro frontends มีมาตั้งแต่ปี 2559 ในการพัฒนาส่วนหน้า โดยสรุป แนวคิดของไมโครฟรอนต์เอนด์คือการแบ่งแอปขนาดใหญ่ออกเป็นชิ้นเล็กๆ สร้างง่ายกว่า และบำรุงรักษาได้มากขึ้น

ที่ช่วยให้คุณ:

  • ปรับใช้อย่างอิสระ
  • ใช้เฟรมเวิร์ก UI หลายอัน (React, Vue.js และ Angular) ในที่เดียว
  • แยกส่วนประกอบ UI ออกจากฐานโค้ดขนาดใหญ่

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

สปาเดี่ยว

single-spa เป็นเฟรมเวิร์กที่ช่วยให้คุณตั้งค่าแอปไมโครฟรอนต์เอนด์ได้อย่างง่ายดาย

ด้วยการสรุปวงจรการใช้งานสำหรับแอปพลิเคชันทั้งหมด single-spa ช่วยให้คุณใช้เฟรมเวิร์ก UI หลายเฟรมในหน้าเดียวกันได้

นอกจากนี้ยังช่วยให้:

  • ปรับใช้อย่างอิสระ
  • ทำการโหลดแบบขี้เกียจ
  • แนะนำเฟรมเวิร์กใหม่โดยไม่มีการปรับเปลี่ยนใดๆ สำหรับแอปที่มีอยู่

ด้วย single-spa คุณมีสองทางเลือกในการเลือกระบบนิเวศสำหรับการแบ่งปันการพึ่งพา:

แนวคิดของการนำเข้าแผนที่คือการแมป URL ของคำสั่งนำเข้า JavaScript ไปยังไลบรารี ตัวอย่างเช่น หากคุณนำเข้า moment ไปยังโค้ดของคุณ

import moment from "moment";

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

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js"
  }
}
</script>

ซึ่งสามารถควบคุมได้ว่า URL ใดจะถูกดึงข้อมูลจากการนำเข้า

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

แต่เราจะไม่ใช้สิ่งนี้ในแอปของเรา เราจะใช้ประโยชน์จากเทคโนโลยีใหม่ใน Webpack 5 แทน

สหพันธ์โมดูลใน Webpack

การรวมโมดูล เปิดตัวใน Webpack 5 วัตถุประสงค์หลักของมันคือการแบ่งปันโค้ดและไลบรารีระหว่างแอปพลิเคชัน

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

คุณอาจสงสัยว่าความแตกต่างระหว่าง micro frontend และ module federation คืออะไร จริงๆ แล้วพวกเขาครอบคลุมพื้นที่เดียวกันมากมาย แต่มีความแตกต่าง

จุดประสงค์เดียวของไมโครฟรอนต์เอนด์คือการแชร์ UI ระหว่างแอปพลิเคชัน ไม่ใช่ตรรกะของ JavaScript ดังนั้นโค้ดที่รวมมาอาจมีโค้ดที่ซ้ำกัน

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

พวกมันเป็นส่วนเสริมจริงๆ ดังนั้น ด้วยการรวมโมดูลในแอปส่วนหน้าแบบไมโคร คุณสามารถหลีกเลี่ยงโค้ดที่ซ้ำกัน และจัดเตรียมส่วนประกอบ UI ที่ใช้ร่วมกันทั่วทั้งแอปได้

ภาพรวมของแอป

ตอนนี้เราได้พูดถึงแนวคิดพื้นฐานของไมโครฟรอนต์เอนด์และการรวมโมดูลแล้ว

ต่อไป เราจะอธิบายการใช้งานแอปสาธิตง่ายๆ เพื่อทำความเข้าใจวิธีการทำงานร่วมกัน

มาดูภาพรวมของสิ่งที่ประกอบเป็นแอปกันดีกว่า:

  • แอปหน้าแรกที่จัดการแอปไมโครฟรอนต์เอนด์ทั้งหมด
  • แอปการนำทางที่แสดง UI ส่วนหัวและส่วนท้ายที่สร้างโดย React
  • แอปเนื้อหาที่แสดงองค์ประกอบเนื้อหาของเพจที่สร้างโดย Vue.js

ตั้งค่ารูท package.json

ก่อนที่จะใช้งานแอปส่วนหน้าแบบไมโคร เราจะสร้าง package.json ที่ระดับราก แอปทั้งสามรวมอยู่ใน repo เดียวในรูปแบบ monorepo ดังนั้นเราจะใช้ yarn workspaces เพื่อแชร์แพ็คเกจบางส่วนทั่วทั้งแอป

สร้าง package.json ที่รูท ดังนี้:

จากนั้น ติดตั้งการขึ้นต่อกัน

yarn

แอปหน้าแรก

ต่อไป เราจะสร้างแอปหน้าแรกที่ประสานงานแอปไมโครฟรอนต์เอนด์แต่ละแอป โฮมจัดการงานที่แสดงผลหน้า HTML และ JavaScript ที่ลงทะเบียนแอปพลิเคชัน

มาดูโครงสร้างบ้านกัน:

├── packages
│   └── home
│       ├── package.json
│       ├── public
│       │   └── index.html
│       ├── src
│       │   └── index.ts
│       ├── tsconfig.json
│       └── webpack.config.js

จากนั้นเราจะสร้างไฟล์เหล่านี้ทีละขั้นตอน

ขั้นแรก สร้าง package.json:

และสร้าง webpack.config.js:

นี่เป็นการกำหนดค่าพื้นฐานสำหรับ TypeScipt แต่คุณสามารถค้นหาการรวมโมดูลที่นำเข้าได้ในส่วนปลั๊กอิน

เพื่อทำความเข้าใจวิธีการทำงาน เราจะกล่าวถึงแนวคิดที่สำคัญสามประการ

รีโมต

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

remotes: {
  navigation: 'navigation',
  body: 'body',
},

เราจะลงรายละเอียดมากขึ้นเมื่อใช้แอปการนำทางและเนื้อหา

เปิดเผย

ฟิลด์ exposes ใช้เพื่อส่งออกไฟล์ไปยังแอปพลิเคชันอื่น คีย์ควรเป็นชื่อที่ส่งออกที่ใช้ในแอปพลิเคชันอื่น ตัวอย่างเช่น หากคุณส่งออกส่วนประกอบ Button จากโค้ดของคุณ ให้เขียนดังนี้:

exposes: {
  Button: './src/Button',
},

แบ่งปัน

shared คือรายการการขึ้นต่อกันทั้งหมดที่ใช้ร่วมกันเพื่อรองรับไฟล์ที่ส่งออก ตัวอย่างเช่น หากคุณส่งออกส่วนประกอบ Vue.js คุณจะต้องแสดงรายการ vue ในส่วนที่ใช้ร่วมกัน ดังนี้:

shared: ['vue'],

วิธีนี้จะป้องกันไม่ให้แอปรวมไลบรารีที่ซ้ำกัน และจะแชร์อินสแตนซ์เดียวระหว่างแอปต่างๆ แม้ว่าคอมไพเลอร์ webpack จะแยกจากกันโดยสิ้นเชิงก็ตาม

ต่อไปเราจะสร้าง public/index.html:

และสร้าง src/index.ts:

ไฟล์นี้จะใช้เพื่อลงทะเบียนแอปไมโครส่วนหน้าที่ใช้ในแอปโฮม ในตอนนี้ เราเพิ่งใช้ฟังก์ชัน start เพื่อเพิ่มประสิทธิภาพของแอป

แอพนำทาง

ตอนนี้เราได้ตั้งค่าแอปหน้าแรกแล้ว เรามาใช้งานแอปนำทางกันดีกว่า

นี่คือโครงสร้างของแอปนำทาง:

├── packages
│   ├── home
│   │   ├── package.json
│   │   ├── public
│   │   │   └── index.html
│   │   ├── src
│   │   │   └── index.ts
│   │   ├── tsconfig.json
│   │   └── webpack.config.js
│   └── navigation
│       ├── package.json
│       ├── src
│       │   ├── Footer.tsx
│       │   ├── Header.tsx
│       │   └── index.ts
│       ├── tsconfig.json
│       ├── webpack.config.js

เราจะส่งออกส่วนประกอบ Footer และ Header ไปยังแอปหน้าแรก

ในการทำเช่นนั้น ขั้นแรกเราจะสร้างไฟล์เหล่านั้น สร้าง navigation/package.json:

แอปการนำทางจะถูกสร้างขึ้นโดย React ดังนั้นให้ติดตั้งการขึ้นต่อกันและสร้าง navigation/webpack.config.js:

ลองดู publicPath และ ModuleFederationPlugin

publicPath เป็นชื่อพื้นฐานของ URL ระยะไกลที่จะใช้ในแอปโฮม ในกรณีนี้ แอปนำทางจะให้บริการที่ http://localshot:3001.

ฟิลด์ exposes ใช้สององค์ประกอบ Header และ Footer การดำเนินการนี้จะส่งออกไปยังแอปโฮม

ตามที่ระบุไว้ก่อนหน้านี้ เราจะต้องแสดงรายการไลบรารีที่แบ่งใช้ในส่วน shared ในกรณีนี้ เราต้องเขียน react, react-dom และ single-spa-react

จากนั้น สร้าง src/Header.tsx และ src/Footer.tsx:

ลงทะเบียนแอปนำทาง

ตอนนี้เราพร้อมที่จะเริ่มส่งออกแอปการนำทางแล้ว

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

  • รวมแท็กสคริปต์
  • รายการในรีโมท
  • เพิ่มการลงทะเบียน
  • รวมคอนเทนเนอร์ div

ทำตามขั้นตอนเหล่านี้กัน

รวมแท็กสคริปต์

ขั้นแรก ในการใช้โค้ดจากแอปนำทาง เราต้องรวมไว้ในไฟล์ HTML

ไปที่ home/public/index.html และรวมแท็กสคริปต์

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="http://localhost:3001/remoteEntry.js"></script>
    ...
</head>

ตามที่ระบุไว้ก่อนหน้านี้ publicPath ของแอปนำทางคือ http://localhost:3001 และชื่อไฟล์คือ remoteEntry.js

รายการในรีโมท

จากนั้นไปที่ home/webpack.config.js และระบุส่วนรีโมท:

remotes: {
  home-nav: 'navigation',
},

navigation คือชื่อของการกำหนดค่าใน navigation/webpack.config.js:

plugins: [
  new ModuleFederationPlugin({
    name: 'navigation',
    library: { type: 'var', name: 'navigation' },
    filename: 'remoteEntry.js',
    remotes: {},
    exposes: {
      Header: './src/Header',
      Footer: './src/Footer',
    },
    shared: ['react', 'react-dom', 'single-spa-react'],
  }),
],

home-nav คือชื่อที่ใช้ในแอปโฮม

คุณสามารถเปลี่ยนชื่อของการนำเข้าได้โดยการเปลี่ยนคีย์เป็นสิ่งที่คุณต้องการ

เพิ่มการลงทะเบียน

ต่อไป เราลงทะเบียนแอปการนำทางใน home/src/index.ts:

registerApplication ใช้เวลาสามสิ่ง:

  • ชื่อแอปพลิเคชัน
  • ฟังก์ชั่นโหลดโค้ดของแอปพลิเคชัน
  • ฟังก์ชันที่กำหนดว่าแอปพลิเคชันจะทำงานหรือไม่ได้ใช้งานเมื่อใด

ฟังก์ชันนำเข้าระบุชื่อของคีย์ remotes ตามที่ระบุไว้ข้างต้น

() => import('home-nav/Header'),

รวม div คอนเทนเนอร์

สุดท้ายนี้ เราเขียนคอนเทนเนอร์ div ที่ใช้ในการรวมส่วนประกอบ Header และ Footer

ไปที่ home/public/index.html และเพิ่ม:

ตามค่าเริ่มต้น หากไม่มีตัวเลือก single-spa จะไปหา single-spa-application:{app name} และแสดงผล HTML ด้านล่าง

ในกรณีนี้ เราได้ลงทะเบียน Header และ Footer เป็น header และ footer แล้ว ดังนั้นจะพบ ID ของ single-spa-application:header และ single-spa-application:footer

มาลองดูกัน

ก่อนหน้านั้น ตรวจสอบให้แน่ใจว่าได้ติดตั้งการขึ้นต่อกัน:

yarn

และเริ่มต้นเซิร์ฟเวอร์ที่รูท:

yarn start

ดังนั้น ไปที่ http://localhost:3000 แล้วคุณจะพบว่าส่วนประกอบ React สองรายการแสดงผลได้สำเร็จ

ดูเหมือนว่าจะทำงานได้ดี

แอปร่างกาย

ต่อไป เราจะสร้างแอปเนื้อหา แอปเนื้อหาจะถูกสร้างขึ้นโดย Vue.js แต่กระบวนการนำไปใช้เกือบจะเหมือนกับกระบวนการในแอปการนำทาง

มาทำกันอย่างรวดเร็ว

โครงสร้างของแอปเนื้อหาคือ:

├── packages
│   ├── body
│   │   ├── package.json
│   │   ├── src
│   │   │   ├── App.vue
│   │   │   ├── app.js
│   │   │   └── index.js
│   │   ├── tsconfig.json
│   │   └── webpack.config.js

สร้าง body/package.json:

และสร้าง body/webpack.config.js:

สร้าง body/src/App.vue และ body/src/app.js:

ลงทะเบียนแอป Body

ต่อไป เราจะลงทะเบียนเนื้อหาในแอปโฮมเหมือนที่เราเคยทำมาก่อนสำหรับการนำทาง

ไปที่ home/public/index.html และเพิ่มแท็กสคริปต์:

<head>
    <script src="http://localhost:3001/remoteEntry.js"></script>
    <script src="http://localhost:3002/remoteEntry.js"></script>
    ...
</head>

จากนั้นไปที่ home/webpack.config.js และเพิ่ม home-body:

remotes: {
  'home-nav': 'navigation',
  'home-body': 'body',
},

จากนั้นไปที่ home/src/index.ts เพื่อลงทะเบียน:

สุดท้าย เพิ่มคอนเทนเนอร์ div สำหรับสิ่งนั้นใน home/public/index.html:

มาทดสอบสิ่งนี้กัน

รันการพึ่งพาการติดตั้ง:

yarn

และเริ่มต้นเซิร์ฟเวอร์ที่รูท:

yarn start

ไปที่ http://localhost:3000:

เยี่ยมมาก คุณจะเห็นว่าคอมโพเนนต์ Vue เรนเดอร์ได้สำเร็จ

บทสรุป

ในบทความนี้ เราได้กล่าวถึงวิธีใช้งานแอปส่วนหน้าแบบไมโครโดยใช้ Single SPA และ Module Federation ใน Webpack

Micro-frontends เป็นประโยชน์ต่อการพัฒนาทีม โดยเฉพาะอย่างยิ่งทีมขนาดใหญ่ในแง่ของความยืดหยุ่น ความสามารถในการขยาย และการบำรุงรักษา เนื่องจากช่วยให้คุณปรับใช้ได้อย่างอิสระ การใช้เฟรมเวิร์กแบบผสม และการแยกข้อกังวล

แม้ว่าจะมีข้อเสีย เช่น ความซับซ้อนของการกำหนดค่าและค่าใช้จ่ายในการเรียนรู้ ฉันคิดว่าข้อดีที่ระบุไว้ข้างต้นมีมากกว่าข้อเสีย

ฉันหวังว่าบทความนี้จะทำให้คุณสนใจส่วนหน้าแบบไมโคร!