Nitro Modules v0.36 ใช้ Hybrid Objects (JSI NativeState) แทน HostObject เดิม ทำให้ GC จัดการ memory pressure ได้แม่นยำขึ้น
Spec เขียนเป็น .nitro.ts แล้ว Nitrogen gen โค้ด C++ glue กับ protocol/interface สำหรับ Swift และ Kotlin ให้อัตโนมัติ
iOS ต่อกับ C++ ผ่าน Swift โดยตรง (ไม่ต้องผ่าน Objective-C), ส่วน Android ใช้ Kotlin + fbjni โดยไม่ต้องเขียน Java
Promise.async ทำงานกับ Swift/Kotlin coroutines ได้เนทีฟ ทำให้ long-running work ปลอดภัยและอ่านง่าย
Zero-copy ArrayBuffer ให้ผลชัดเจนบน payload ใหญ่ (image, file, crypto), ประมาณ 8–40% เร็วกว่า TurboModules
รองรับ Nitro Views บน Fabric สำหรับ RN 0.78+ ใช้แทน Fabric Native Component ที่โค้ดเยอะกว่ามาก
หัวข้อในหน้านี้
Nitro Modules คืออะไร และต่างจาก TurboModules อย่างไร
ข้อกำหนดเบื้องต้นและเวอร์ชันที่รองรับ
ติดตั้ง Nitro และตั้งค่า nitro.json
เขียน .nitro.ts spec สำหรับ Hybrid Object
Implement บน iOS ด้วย Swift และ Promise.async
Implement บน Android ด้วย Kotlin coroutines
ลงทะเบียนใน HybridObjectRegistry และใช้งานจาก JS
Nitro Views: สร้าง Fabric View แบบสั้นและ type-safe
ใช้ Nitro Modules กับ Expo และ prebuild
Pitfalls, JNI crash และ workflow การ regen spec
เมื่อไหร่ที่ไม่ควรใช้ Nitro Modules
คำถามที่พบบ่อย
Nitro Modules คืออะไร และต่างจาก TurboModules อย่างไร
ในทีมที่ผมดูแล เราย้าย native module บางตัวจาก TurboModules ไป Nitro ในไตรมาสแรกของ 2026 เหตุผลหลักไม่ใช่แค่ความเร็ว แต่คือ type safety จริง ๆ ระหว่าง TS ↔ Swift ↔ Kotlin . spec เขียนที่เดียว codegen ผูก contract เดียวกันทั้งสองฝั่งของ bridgeless boundary
Nitro Modules ถูกออกแบบโดย Marc Rousavy (คนเดียวกับ VisionCamera และ MMKV V4) ให้เป็นชั้นที่บางกว่า TurboModules ทั้งในโค้ดและ runtime ต่างกันสำคัญ ๆ ได้แก่:
Codegen แยกจาก core: Nitrogen ทำ codegen ของตัวเอง ไม่ต้องรอ RN core release cycle การเพิ่ม type ใหม่ เช่น union หรือ ArrayBuffer จึงมาถึง user เร็วกว่า
Hybrid Objects แทน HostObject: ใช้ jsi::NativeState ที่บอก JS engine ได้ว่าสถานะเนทีฟกินหน่วยความจำเท่าไร ทำให้ Hermes GC ตัดสินใจได้แม่นขึ้น (ประเด็นนี้จริงจังในแอปที่มี image buffer เยอะ)
Swift แบบตรง ๆ: ฝั่ง iOS ไม่ต้องเขียน Objective-C wrapper ให้ Swift Nitro ใช้ Swift-C++ interop ที่ Apple เปิดตัวมาตั้งแต่ Xcode 15
Kotlin แบบตรง ๆ: ฝั่ง Android ใช้ Kotlin + fbjni ตรงกับ C++ ไม่ต้องเขียน Java bridge ระดับกลาง
ถ้าอยากเห็นภาพรวมของ New Architecture ที่ Nitro นั่งอยู่ข้างบน อ่านต่อได้ใน คู่มือ React Native New Architecture 2025-2026: Fabric, TurboModules, JSI และ Hermes บทความนั้นอธิบาย JSI, bridgeless mode และ Fabric ที่ Nitro ใช้เป็นฐาน
ตัวเลขที่คนอ้างบ่อย ๆ (100k calls บน iPhone 15 Pro): Nitro 7.27ms, TurboModules 115.86ms, Expo Modules 434.85ms สำหรับ addNumbers ผมย้ำเสมอว่าตัวเลขนี้เป็น micro-benchmark เมื่อ workload จริง (I/O + compression + serialization) ช่องว่าง Turbo↔Nitro หดเหลือประมาณ 8–40% ไม่ใช่ 15×
ข้อกำหนดเบื้องต้นและเวอร์ชันที่รองรับ
ก่อนเริ่มโปรเจ็กต์ Nitro Module ตรวจสิ่งเหล่านี้ก่อน จากประสบการณ์การอัปเกรด monorepo หลายตัวในปีนี้ ปัญหา 80% ตอน start มาจาก mismatch ของเวอร์ชัน:
React Native ≥ 0.78 พร้อม newArchEnabled=true (Nitro Views ต้องการ Fabric)
Xcode ≥ 15.3 เพื่อใช้ Swift-C++ interop แบบ mixed target
Android Gradle Plugin ≥ 8.5 , Kotlin ≥ 2.0 (Nitro ใช้ K2 compiler features)
Node ≥ 20 สำหรับ Nitrogen CLI
Hermes: แนะนำเปิด ใช้ JSC ได้แต่จะเสีย NativeState memory hints บางส่วน
Warning: Nitro Modules v0.35.0 ลบ deprecated API หลายตัว และ v0.36 ต้อง regen Nitrogen ใหม่ทั้งหมด ถ้าอัปเดต library ที่ pin ไว้ที่ 0.34.x คุณต้องรัน nitrogen ใหม่และ rebuild ทั้ง native project มิฉะนั้นจะ crash ตอน register hybrid object
ทีมที่ยังใช้ Old Architecture ใช้ Nitro Modules ไม่ได้ เพราะ Nitro ผูกกับ JSI runtime และ Fabric renderer โดยตรง ถ้ายังต้อง support paper renderer อยู่ ให้ใช้ TurboModules ที่มี supportsInterop ไปก่อน แล้วค่อยย้ายเมื่อ opt-in เสร็จ
ติดตั้ง Nitro และตั้งค่า nitro.json
เอาจริง ๆ วิธีที่เร็วที่สุดคือใช้ template ของ create-react-native-library ซึ่ง Callstack ใส่ Nitro entry มาให้ตั้งแต่ต้นปี 2026 อ่าน rationale ของ bridgeless-first tooling ได้จาก Callstack: Bridgeless Native Development
# สร้าง library ใหม่แบบ Nitro
npx create-react-native-library@latest react-native-image-resizer \
--type nitro-module \
--languages kotlin-swift
cd react-native-image-resizer
yarn
ในโปรเจ็กต์แอปที่จะใช้งาน ติดตั้งทั้ง runtime และ library ของคุณ:
yarn add react-native-nitro-modules react-native-image-resizer
cd ios && pod install
ไฟล์ nitro.json ที่รูทของ library คือหัวใจของ autolinking, บอกให้ Nitrogen รู้ว่าจะ scan spec ไหน gen C++ ไปไว้ที่ไหน และแมป platform interface ยังไง
{
"cxxNamespace": ["margelo", "nitro", "imageresizer"],
"ios": {
"iosModuleName": "ImageResizer"
},
"android": {
"androidNamespace": ["imageresizer"],
"androidCxxLibName": "ImageResizer"
},
"autolinking": {
"ImageResizer": {
"swift": "HybridImageResizer",
"kotlin": "HybridImageResizer"
}
}
}
autolinking key คือชื่อ Hybrid Object ที่ JS จะเรียก ส่วน swift/kotlin คือชื่อ class ที่ Nitrogen จะสร้าง protocol/abstract class ให้เรา implement ต่อ
เขียน .nitro.ts spec สำหรับ Hybrid Object
Spec คือ contract แบบ TypeScript ที่ Nitrogen ใช้ generate ทั้งฝั่ง C++ glue และ signature ให้ Swift/Kotlin หัวใจอยู่ที่ interface ที่ extends HybridObject<T> ตรง T ให้บอกว่าจะ implement ด้วยภาษาอะไรบน platform ไหน
ตัวอย่างจริง ผมยกงานที่พบบ่อยที่สุดในแอป fintech ของทีม, resize รูปก่อนอัปโหลด KYC ซึ่งควรทำใน native เพราะ image buffer ใหญ่และ blocking JS thread ไม่ได้:
// src/specs/ImageResizer.nitro.ts
import type { HybridObject } from 'react-native-nitro-modules'
export interface ImageResizer
extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
// metadata property (getter อัตโนมัติ)
readonly maxTextureSize: number
// sync, เหมาะกับงานเล็ก ๆ ไม่ blocking
computeAspectRatio(width: number, height: number): number
// async, ใช้ Promise ให้ Nitrogen gen coroutines ให้ทั้งสองฝั่ง
resize(
input: ArrayBuffer,
targetWidth: number,
targetHeight: number,
quality: number
): Promise<ArrayBuffer>
}
รัน Nitrogen เพื่อ generate โค้ด, output จะไปอยู่ที่ nitrogen/generated/:
yarn nitrogen
# หรือถ้า pin เวอร์ชัน
yarn nitro-codegen
Tip: ผูก nitrogen เข้ากับ prepare ของ package.json ให้เพื่อนใน monorepo ไม่ต้องมานั่ง run manual ทุกครั้งที่ pull spec ใหม่ ผมชอบ pattern "prepare": "bob build && nitrogen" (ป้องกัน drift ระหว่าง JS types กับ native signatures)
Implement บน iOS ด้วย Swift และ Promise.async
Nitrogen จะ generate HybridImageResizerSpec ให้เป็น Swift protocol ที่เรา conform โดยไม่ต้องแตะ Objective-C เลย ใช้ Accelerate framework (vImage) เพื่อ resize บน CPU อย่างมีประสิทธิภาพ:
// ios/HybridImageResizer.swift
import Accelerate
import NitroModules
class HybridImageResizer: HybridImageResizerSpec {
var maxTextureSize: Double { 8192 }
func computeAspectRatio(width: Double, height: Double) throws -> Double {
guard height > 0 else { throw RuntimeError.error(withMessage: "height=0") }
return width / height
}
func resize(
input: ArrayBufferHolder,
targetWidth: Double,
targetHeight: Double,
quality: Double
) throws -> Promise<ArrayBufferHolder> {
// Promise.async รันบน background queue โดยอัตโนมัติ
return Promise.async {
let bytes = input.data
let count = input.size
// อ่านเป็น UIImage → resize → เขียนกลับเป็น JPEG
guard let src = self.decode(bytes: bytes, count: count) else {
throw RuntimeError.error(withMessage: "decode failed")
}
let size = CGSize(width: targetWidth, height: targetHeight)
let resized = self.resizeUsingVImage(src, to: size)
guard let jpeg = resized.jpegData(compressionQuality: quality) else {
throw RuntimeError.error(withMessage: "jpeg encode failed")
}
// คืนเป็น ArrayBuffer ไปฝั่ง JS แบบ zero-copy
return ArrayBufferHolder.copy(data: jpeg)
}
}
}
สังเกตว่า Promise.async { ... } รัน closure บน Swift Concurrency executor. ถ้ามี await ในนั้นก็เขียนได้ตรง ๆ Nitrogen แปลง throw ให้กลายเป็น JS Promise rejection อัตโนมัติ ไม่ต้อง manual reject
Implement บน Android ด้วย Kotlin coroutines
ฝั่ง Android เขียน Kotlin ตรงกับ Nitrogen-generated abstract class โดยไม่มี Java layer กลาง เลือกใช้ coil หรือ BitmapFactory ตามความสะดวก ผมชอบ BitmapFactory + Bitmap.createScaledBitmap เพราะ dependency น้อย:
// android/src/main/java/com/imageresizer/HybridImageResizer.kt
package com.imageresizer
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.margelo.nitro.core.ArrayBuffer
import com.margelo.nitro.core.Promise
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
class HybridImageResizer : HybridImageResizerSpec() {
override val maxTextureSize: Double = 8192.0
override fun computeAspectRatio(width: Double, height: Double): Double {
require(height > 0) { "height=0" }
return width / height
}
override fun resize(
input: ArrayBuffer,
targetWidth: Double,
targetHeight: Double,
quality: Double
): Promise<ArrayBuffer> = Promise.async {
withContext(Dispatchers.IO) {
val bytes = ByteArray(input.size).also {
input.getBuffer(false).get(it)
}
val src = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
?: error("decode failed")
val scaled = Bitmap.createScaledBitmap(
src, targetWidth.toInt(), targetHeight.toInt(), true
)
val out = ByteArrayOutputStream()
scaled.compress(Bitmap.CompressFormat.JPEG, (quality * 100).toInt(), out)
val encoded = out.toByteArray()
// ArrayBuffer.copy จะ allocate direct buffer ไปฝั่ง JS
val buf = ByteBuffer.allocateDirect(encoded.size).put(encoded)
ArrayBuffer.wrap(buf)
}
}
}
Warning: ถ้าเปิด coroutine ที่ยาวกว่า lifecycle ของ HybridObject (เช่น register callback ต่อ Bluetooth event) ให้ระวัง crash jni::global_ref ที่ mrousavy/nitro#672 พูดถึง (v0.35 แก้แล้วด้วย weak_ptr-based CxxPart) แต่ pattern ที่ปลอดภัยที่สุดคือ scope coroutine เข้ากับ onDestroy() ของ Hybrid Object เสมอ
ลงทะเบียนใน HybridObjectRegistry และใช้งานจาก JS
Autolinking จะ register class ให้อัตโนมัติในกรณีทั่วไป แต่ถ้ามีหลาย instance หรืออยาก inject dependency จาก DI container ให้ register manually ใน entry ของ platform:
// ios/ImageResizerPackage.swift
import NitroModules
@objc(ImageResizerPackage)
class ImageResizerPackage: NSObject {
@objc static func registerNitroTypes() {
HybridObjectRegistry.registerHybridObjectConstructor("ImageResizer") {
HybridImageResizer()
}
}
}
// android/src/main/java/com/imageresizer/ImageResizerPackage.kt
import com.margelo.nitro.core.HybridObjectRegistry
object ImageResizerPackage {
fun registerNitroTypes() {
HybridObjectRegistry.registerHybridObjectConstructor("ImageResizer") {
HybridImageResizer()
}
}
}
ฝั่ง TypeScript สร้าง facade ตัวเดียวเพื่อไม่ให้ consumer เห็น NitroModules API ตรง ๆ ทำให้ mock ใน test ง่ายและเปลี่ยน implementation ในอนาคตได้ (พอ MMKV V4 ทำแบบนี้ก็ทำให้ migration จาก v3 เรียบมาก อ่านเรื่อง storage ทั้งหมดได้ใน คู่มือจัดเก็บข้อมูล React Native 2026: MMKV V4, AsyncStorage, expo-sqlite ):
// src/index.ts
import { NitroModules } from 'react-native-nitro-modules'
import type { ImageResizer as Spec } from './specs/ImageResizer.nitro'
let cached: Spec | null = null
export function getImageResizer(): Spec {
if (!cached) {
cached = NitroModules.createHybridObject<Spec>('ImageResizer')
}
return cached
}
export async function resizeJpeg(
input: ArrayBuffer,
w: number,
h: number,
quality = 0.85
) {
return getImageResizer().resize(input, w, h, quality)
}
Nitro Views: สร้าง Fabric View แบบสั้นและ type-safe
ก่อน Nitro Views ออกมาปลายปี 2025 การเขียน Fabric Native Component ต้องแตะ 6–7 ไฟล์ต่อ platform ได้แก่ CodegenSchema, ComponentDescriptor, ShadowNode, EventEmitter และอื่น ๆ Nitro Views ยุบทั้งหมดให้เหลือ spec เดียวกับ interface เดียว
// src/specs/BlurView.nitro.ts
import type { HybridView, HybridViewProps } from 'react-native-nitro-modules'
export interface BlurViewProps extends HybridViewProps {
radius: number
tint: 'light' | 'dark' | 'systemMaterial'
}
export interface BlurViewMethods {
refresh(): void
}
export type BlurView = HybridView<BlurViewProps, BlurViewMethods>
// src/BlurView.tsx
import { getHostComponent } from 'react-native-nitro-modules'
import type { BlurView, BlurViewProps } from './specs/BlurView.nitro'
const BlurViewComponent = getHostComponent<BlurViewProps, BlurView>('BlurView')
export function AppBlurView(props: BlurViewProps) {
return <BlurViewComponent {...props} />
}
ฝั่ง native implement HybridBlurViewSpec ก็เป็น UIView/ViewGroup ปกติ Nitro จัดการ prop diffing และ event emitting ให้ผ่าน Fabric renderer ผมย้ายชิ้น bottom sheet + skeleton loader ในแอปด้วย pattern นี้แล้วโค้ดหายไปเกิน 60%
ใช้ Nitro Modules กับ Expo และ prebuild
Nitro Modules ใช้กับ Expo ได้ตั้งแต่ SDK 51 ขึ้นไป โดยเงื่อนไขคือต้อง ใช้ prebuild หรือ development build เพราะ Nitro ต้องแทรกโค้ด native หลัง pod install/gradle sync Expo Go ปกติใช้ไม่ได้
ในโปรเจ็กต์ Expo ทั่วไป workflow ที่ผมใช้:
ติดตั้ง library ใน package.json ปกติ
ถ้า library ต้องการ config plugin (เช่น permission) เพิ่มใน app.json
รัน npx expo prebuild --clean เพื่อ regenerate ios/android
build ผ่าน eas build --profile development. อ่าน pipeline เต็มได้ใน คู่มือ EAS Build, Submit และ Update 2026
// app.json (ตัวอย่าง)
{
"expo": {
"plugins": [
"react-native-image-resizer/plugin"
],
"newArchEnabled": true
}
}
Note: ถ้าเจอ error Cannot find module 'react-native-nitro-modules' ตอน Metro start ให้เช็คว่า newArchEnabled เป็น true ทั้งใน app.json และใน ios/Podfile.properties.json. Expo prebuild บางเวอร์ชันไม่ propagate flag นี้ให้อัตโนมัติ ต้อง set ทั้งคู่
Pitfalls, JNI crash และ workflow การ regen spec
เอาล่ะ ปัญหาที่ผมเจอบ่อยที่สุดตอน audit codebase ของทีมอื่น (และตอน onboarding เพื่อนใหม่เข้าทีมตัวเอง) ก็มีสี่ข้อหลัก ๆ นี้แหละ:
ลืมรัน Nitrogen หลังแก้ spec: C++ glue จะไม่ตรงกับ native class ทำให้ symbol not found ตอน link แก้ด้วยการใส่ nitrogen ใน pre-commit hook หรือ bob build pipeline
Android JNI thread-attach crash: ถ้าเรียก JS callback จาก coroutine ที่รันนอก main dispatcher โดยไม่ใช้ Promise/Callback helper ที่ Nitro ให้ จะเจอ crash attaching to VM failed ให้ผ่าน NitroModules.currentRuntime หรือใช้ Callback primitive แทนการ hold JSI ref เอง
ArrayBuffer ที่ hold ไว้ยาวเกิน: ถ้าคุณเก็บ ArrayBufferHolder ไว้เป็น property นาน ๆ JS side อาจ GC underlying memory ก่อน ให้ copy ทันทีที่ได้รับ ถ้าจะ hold ยาว
Deprecated API ระหว่าง v0.34 → v0.36: THIS pointer capture pattern เก่าใช้ไม่ได้ ต้องเปลี่ยนเป็น weak_ptr-based CxxPart ทั้งหมด
สำหรับการ debug ผมใช้ React Native DevTools + Hermes profiler เป็นหลัก. ถ้ายังไม่เคยตั้งค่า อ่าน React Native DevTools 2026: คู่มือดีบักแอปฉบับสมบูรณ์ ก่อน จะช่วยหา bottleneck ก่อนตัดสินใจว่าจะย้ายไป Nitro จริง ๆ ไหม
เมื่อไหร่ที่ไม่ควรใช้ Nitro Modules
เพราะเป็น architect ผมโดนถามคำถามนี้บ่อยกว่า "จะย้ายเมื่อไร" ด้วยซ้ำ Nitro Modules ไม่ใช่ silver bullet:
ต้อง support Old Architecture: ใช้ TurboModules ที่มี fallback ไปได้ก่อน
ต้องใช้ Expo Go: Nitro ต้อง native build เท่านั้น. Expo Go จะ error ทันที
Team ไม่มีคนเขียน Swift/Kotlin: Nitro ตัดชั้น Obj-C/Java ออก ถ้าไม่มีคนอ่านโค้ดสองภาษาใหม่นี้ maintenance cost จะสูง
Library ที่ Turbo/Expo Modules ทำงานได้ดีอยู่แล้ว: ประโยชน์จากการย้ายอาจไม่คุ้ม refactor cost โดยเฉพาะ library ที่ไม่ hot-path
งานที่ blocking JS thread ไม่ใช่ปัญหา: เช่น setup flow ที่ทำครั้งเดียว การได้ 15× ตรงนั้นไม่รู้สึก
เกณฑ์ตัดสินใจของผม: ย้ายเมื่อ (1) native ถูกเรียกในลูป (frame processor, gesture handler), (2) payload ใหญ่ (image, file), หรือ (3) มี type safety issue ระหว่าง TS ↔ native ที่ทำ bug production มาก่อน ในกรณีอื่น TurboModules/Expo Modules ก็เพียงพอ ถ้าเทียบ list rendering performance ที่ user รู้สึก impact โดยตรง อ่าน FlashList v2 vs FlatList vs LegendList 2026 ประกอบ. บางที bottleneck จริงอยู่ที่ render ไม่ใช่ native bridge
เอกสารเป็นทางการที่ผมเปิดค้างไว้ตอนเขียน spec: Nitro Modules Documentation และ mrousavy/nitro on GitHub . ทั้งสอง update ค่อนข้างเร็ว ให้ track release notes อย่างน้อยเดือนละครั้ง
คำถามที่พบบ่อย
Nitro Modules ใช้ใน production ได้หรือยังในปี 2026?
ได้ครับ v0.36.1 stable และ MMKV V4, VisionCamera V5 รวมถึง library อื่นในเครือ Margelo ใช้ใน production มาแล้วเกินหนึ่งปี ข้อควรระวังคือ API ยังไม่ 1.0 อาจมี breaking change เล็ก ๆ ในระดับ minor version. pin version และ read changelog ทุกครั้งที่อัปเดต
Nitro Modules ทำงานร่วมกับ Expo ได้ไหม?
ได้ ตั้งแต่ Expo SDK 51 ขึ้นไป โดยต้องใช้ expo prebuild หรือ EAS development build เท่านั้น Expo Go ใช้ไม่ได้เพราะ Nitro ต้องแทรก native code หลัง prebuild/pod install
Nitro Modules รันบน Old Architecture ได้ไหม?
ไม่ได้ Nitro ผูกกับ JSI runtime + Fabric renderer โดยตรง ต้องเปิด New Architecture (newArchEnabled=true) และใช้ React Native ≥ 0.78 ถ้ายัง support Old Architecture อยู่ ให้ใช้ TurboModules ก่อน
Nitro Modules เร็วกว่า TurboModules เท่าไร (ตัวเลขจริง)?
บน micro-benchmark 100k calls บน iPhone 15 Pro Nitro เร็วประมาณ 15× (Nitro 7.27ms vs TurboModules 115.86ms สำหรับ addNumbers) บน workload จริงที่มี I/O หรือ compression ช่องว่างหดเหลือประมาณ 8–40% ให้ profile ของแอปตัวเองก่อนตัดสินใจย้าย
ต้องรู้ Swift และ Kotlin ก่อนใช้ Nitro Modules ไหม?
ควรครับ Nitro ตัดชั้น Obj-C และ Java ออกทั้งหมด ทำให้ผู้ใช้ต้องเขียน Swift บน iOS และ Kotlin บน Android โดยตรง ทีมที่ไม่มีคนอ่านสองภาษานี้ต้อง onboarding คน mobile native หรือใช้ TurboModules ที่ทีมเดิม maintain ได้อยู่
Nitro Views ต่างจาก Fabric Native Component เดิมยังไง?
Nitro Views ใช้ spec ไฟล์เดียว (.nitro.ts) แทนการเขียน CodegenSchema + ComponentDescriptor + ShadowNode + EventEmitter แยกกัน ปริมาณโค้ดลดลงประมาณ 60–70% ต่อ component โดยยังใช้ Fabric renderer เหมือนเดิม จึงได้ประสิทธิภาพเท่ากันแต่ maintenance cost ต่ำกว่ามาก