ไทย

ปลดล็อกพลังของ Micro-frontends ด้วย JavaScript Module Federation ใน Webpack 5 เรียนรู้วิธีสร้างเว็บแอปพลิเคชันที่ขยายขนาดได้ บำรุงรักษาง่าย และทำงานได้อย่างอิสระ

JavaScript Module Federation กับ Webpack 5: คู่มือฉบับสมบูรณ์สำหรับ Micro-frontends

ในวงการพัฒนาเว็บที่เปลี่ยนแปลงตลอดเวลา การสร้างแอปพลิเคชันขนาดใหญ่และซับซ้อนอาจเป็นงานที่น่าท้าทาย สถาปัตยกรรมแบบ Monolithic แบบดั้งเดิมมักนำไปสู่เวลาในการพัฒนาที่เพิ่มขึ้น ปัญหาคอขวดในการติดตั้งใช้งาน และความท้าทายในการรักษาคุณภาพของโค้ด Micro-frontends ได้ถือกำเนิดขึ้นมาเป็นรูปแบบสถาปัตยกรรมที่ทรงพลังเพื่อจัดการกับความท้าทายเหล่านี้ โดยช่วยให้ทีมสามารถสร้างและติดตั้งใช้งานส่วนต่างๆ ของเว็บแอปพลิเคชันขนาดใหญ่ได้อย่างอิสระ หนึ่งในเทคโนโลยีที่มีแนวโน้มดีที่สุดสำหรับการนำ Micro-frontends ไปใช้คือ JavaScript Module Federation ซึ่งเปิดตัวใน Webpack 5

Micro-frontends คืออะไร?

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

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

ประโยชน์ของ Micro-frontends:

ความท้าทายของ Micro-frontends:

JavaScript Module Federation คืออะไร?

JavaScript Module Federation คือฟีเจอร์ของ Webpack 5 ที่ช่วยให้คุณสามารถแบ่งปันโค้ดระหว่างแอปพลิเคชัน JavaScript ที่คอมไพล์แยกกันได้ในขณะทำงาน (runtime) มันช่วยให้คุณสามารถเปิดเผย (expose) ส่วนต่างๆ ของแอปพลิเคชันของคุณเป็น "โมดูล" ที่แอปพลิเคชันอื่นสามารถนำไปใช้ (consume) ได้ โดยไม่จำเป็นต้องเผยแพร่ไปยังคลังเก็บส่วนกลางอย่าง npm

ลองนึกภาพ Module Federation เป็นวิธีการสร้างระบบนิเวศของแอปพลิเคชันแบบรวมศูนย์ (federated ecosystem) ที่แต่ละแอปพลิเคชันสามารถนำเสนอฟังก์ชันการทำงานของตนเองและใช้ฟังก์ชันการทำงานจากแอปพลิเคชันอื่นได้ ซึ่งช่วยลดความจำเป็นในการพึ่งพา (dependencies) ณ เวลาสร้าง (build-time) และช่วยให้สามารถติดตั้งใช้งานได้อย่างอิสระอย่างแท้จริง

ตัวอย่างเช่น ทีมระบบการออกแบบ (design system team) สามารถเปิดเผย UI components เป็นโมดูล และทีมแอปพลิเคชันต่างๆ สามารถใช้คอมโพเนนต์เหล่านี้ได้โดยตรงจากแอปพลิเคชันระบบการออกแบบ โดยไม่จำเป็นต้องติดตั้งเป็นแพ็คเกจ npm เมื่อทีมระบบการออกแบบอัปเดตคอมโพเนนต์ การเปลี่ยนแปลงจะสะท้อนไปยังแอปพลิเคชันที่ใช้งานทั้งหมดโดยอัตโนมัติ

แนวคิดหลักใน Module Federation:

การตั้งค่า Module Federation กับ Webpack 5: คู่มือเชิงปฏิบัติ

เรามาดูตัวอย่างการตั้งค่า Module Federation กับ Webpack 5 กัน เราจะสร้างแอปพลิเคชันง่ายๆ สองตัว: แอปพลิเคชัน Host และแอปพลิเคชัน Remote แอปพลิเคชัน Remote จะเปิดเผยคอมโพเนนต์ และแอปพลิเคชัน Host จะนำไปใช้งาน

1. การตั้งค่าโปรเจกต์

สร้างไดเรกทอรีแยกกันสองแห่งสำหรับแอปพลิเคชันของคุณ: `host` และ `remote`

```bash mkdir host remote cd host npm init -y npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev npm install react react-dom cd ../remote npm init -y npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev npm install react react-dom ```

2. การกำหนดค่าแอปพลิเคชัน Remote

ในไดเรกทอรี `remote` ให้สร้างไฟล์ต่อไปนี้:

src/index.js:

```javascript import React from 'react'; import ReactDOM from 'react-dom/client'; import RemoteComponent from './RemoteComponent'; const App = () => (

Remote Application

); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(); ```

src/RemoteComponent.jsx:

```javascript import React from 'react'; const RemoteComponent = () => (

This is a Remote Component!

Rendered from the Remote Application.

); export default RemoteComponent; ```

webpack.config.js:

```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const path = require('path'); module.exports = { entry: './src/index', mode: 'development', devServer: { port: 3001, static: { directory: path.join(__dirname, 'dist'), }, }, output: { publicPath: 'auto', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'remote', filename: 'remoteEntry.js', exposes: { './RemoteComponent': './src/RemoteComponent', }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { extensions: ['.js', '.jsx'], }, }; ```

สร้างไฟล์ `public/index.html` พร้อมโครงสร้าง HTML พื้นฐาน สิ่งสำคัญคือ `

`

3. การกำหนดค่าแอปพลิเคชัน Host

ในไดเรกทอรี `host` ให้สร้างไฟล์ต่อไปนี้:

  • `src/index.js`: จุดเริ่มต้นของแอปพลิเคชัน
  • `webpack.config.js`: ไฟล์กำหนดค่าของ Webpack

src/index.js:

```javascript import React, { Suspense } from 'react'; import ReactDOM from 'react-dom/client'; const RemoteComponent = React.lazy(() => import('remote/RemoteComponent')); const App = () => (

Host Application

Loading Remote Component...
}>
); const root = ReactDOM.createRoot(document.getElementById('root')); root.render(); ```

webpack.config.js:

```javascript const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); const path = require('path'); module.exports = { entry: './src/index', mode: 'development', devServer: { port: 3000, static: { directory: path.join(__dirname, 'dist'), }, }, output: { publicPath: 'auto', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react', '@babel/preset-env'], }, }, }, ], }, plugins: [ new ModuleFederationPlugin({ name: 'host', remotes: { remote: 'remote@http://localhost:3001/remoteEntry.js', }, shared: { react: { singleton: true, eager: true }, 'react-dom': { singleton: true, eager: true }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), ], resolve: { extensions: ['.js', '.jsx'], }, }; ```

สร้างไฟล์ `public/index.html` พร้อมโครงสร้าง HTML พื้นฐาน (คล้ายกับแอป remote) สิ่งสำคัญคือ `

`

4. ติดตั้ง Babel

ในทั้งสองไดเรกทอรี `host` และ `remote` ให้ติดตั้ง dependencies ของ Babel:

```bash npm install --save-dev @babel/core @babel/preset-env @babel/preset-react babel-loader ```

5. รันแอปพลิเคชัน

ในทั้งสองไดเรกทอรี `host` และ `remote` ให้เพิ่มสคริปต์ต่อไปนี้ใน `package.json`:

```json "scripts": { "start": "webpack serve" } ```

ตอนนี้ ให้เริ่มแอปพลิเคชันทั้งสอง:

```bash cd remote npm start cd ../host npm start ```

เปิดเบราว์เซอร์ของคุณและไปที่ `http://localhost:3000` คุณควรเห็นแอปพลิเคชัน Host พร้อมกับ Remote Component ที่แสดงผลอยู่ภายใน

คำอธิบายตัวเลือกการกำหนดค่าที่สำคัญ:

เทคนิค Module Federation ขั้นสูง

Module Federation มีฟีเจอร์ขั้นสูงมากมายที่สามารถช่วยคุณสร้างสถาปัตยกรรม micro-frontend ที่ซับซ้อนยิ่งขึ้นได้

Dynamic Remotes

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

ตัวอย่างเช่น คุณสามารถเก็บ URL ของแอปพลิเคชัน remote ไว้ในไฟล์กำหนดค่าหรือฐานข้อมูลและโหลดแบบไดนามิกโดยใช้ JavaScript

```javascript // In webpack.config.js remotes: { remote: `promise new Promise(resolve => { const urlParams = new URLSearchParams(window.location.search); const remoteUrl = urlParams.get('remote'); // Assume remoteUrl is something like 'http://localhost:3001/remoteEntry.js' const script = document.createElement('script'); script.src = remoteUrl; script.onload = () => { // the key of module federation is that the remote app is // available using the name in the remote resolve(window.remote); }; document.head.appendChild(script); })`, }, ```

ตอนนี้คุณสามารถโหลดแอป host ด้วยพารามิเตอร์คิวรี `?remote=http://localhost:3001/remoteEntry.js`

Versioned Shared Modules

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

คุณสามารถระบุช่วงเวอร์ชันของแต่ละโมดูลที่ใช้ร่วมกันในไฟล์กำหนดค่าของ Webpack

```javascript // In webpack.config.js shared: { react: { singleton: true, eager: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }, }, ```

Custom Module Loaders

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

การแชร์สถานะ (State) ระหว่าง Micro-frontends

หนึ่งในความท้าทายของสถาปัตยกรรม micro-frontend คือการแชร์สถานะระหว่าง micro-frontend ที่แตกต่างกัน มีหลายแนวทางที่คุณสามารถใช้เพื่อแก้ไขปัญหานี้:

แนวทางปฏิบัติที่ดีที่สุดสำหรับการนำ Micro-frontends ไปใช้กับ Module Federation

ต่อไปนี้คือแนวทางปฏิบัติที่ดีที่สุดที่ควรคำนึงถึงเมื่อนำ micro-frontends ไปใช้กับ Module Federation:

ตัวอย่างการใช้งาน Module Federation ในโลกแห่งความเป็นจริง

แม้ว่ากรณีศึกษาที่เฉพาะเจาะจงมักเป็นความลับ แต่ต่อไปนี้คือสถานการณ์ทั่วไปที่ Module Federation มีประโยชน์อย่างยิ่ง:

สรุป

JavaScript Module Federation ใน Webpack 5 เป็นวิธีที่ทรงพลังและยืดหยุ่นในการสร้างสถาปัตยกรรม micro-frontend ช่วยให้คุณสามารถแบ่งปันโค้ดระหว่างแอปพลิเคชัน JavaScript ที่คอมไพล์แยกกันได้ในขณะทำงาน ทำให้สามารถติดตั้งใช้งานได้อย่างอิสระ มีความหลากหลายทางเทคโนโลยี และเพิ่มความเป็นอิสระของทีม โดยการปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดที่ระบุไว้ในคู่มือนี้ คุณสามารถใช้ประโยชน์จาก Module Federation เพื่อสร้างเว็บแอปพลิเคชันที่ขยายขนาดได้ บำรุงรักษาง่าย และมีนวัตกรรม

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